diff --git a/README.md b/README.md
index b99dec5..d7ff3b8 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ opm install winow
- Работать с шаблонами ответов (Синтаксис шаблона чем-то похож на jinja2, но сильно упрощен).
- Базовая авторизация и управление доступом к страницам по ролям.
- Использовать протокол WebSocket
+- Использовать протокол server-sent events. (SSE)
## Ограничения ?
@@ -1180,6 +1181,102 @@ app/КонтролСУправлениемДоступом.os
![ws](docs/ws-chat.gif)
+# Работа с механизмом server-sent events.
+
+Подробно можно прочитать на [вики](https://ru.wikipedia.org/wiki/Server-sent_events).
+
+Для реализации работы с механизмом нужно выполнить несколько шагов.
+
+1. Зарегистрировать топик, в конструкторе контроллера.
+2. Добавить обработчики событий, которые будут вызываться при подключении и отключении клиента. (опционально)
+3. Посылать сообщения клиенту в топики, при необходимости в соответствии с логикой приложения.
+
+Рассмотрим на примере:
+
+```bsl
+
+// Подключаем необходимые зависимости
+&Пластилин Перем БрокерСообщенийСобытийСервера;
+&пластилин Перем ФабрикаОтветов;
+
+&Контроллер("/sse")
+&Отображение(Шаблон = "./hwapp/view/main_sse.html")
+Процедура ПриСозданииОбъекта(&Пластилин ТопикиСерверныхСобытий)
+
+ ИмяТопика = "/sse/acorndiscussion";
+
+ // регистрируем топик и обработчики открытия и закрытия
+ ТопикиСерверныхСобытий.Добавить(ИмяТопика,
+ Новый Действие(ЭтотОбъект, "НовоеПодключениеССЕ"),
+ Новый Действие(ЭтотОбъект, "ОтключениеССЕ"));
+
+КонецПроцедуры
+
+Процедура НовоеПодключениеССЕ(Сессия, ИД) Экспорт
+
+ // Код обработчика открытия соединения
+
+КонецПроцедуры
+
+Процедура ОтключениеССЕ(Сессия, ИД) Экспорт
+
+ // Код обработчика закрытия соединения
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщение()
+ // Создаем сообщение
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("like");
+ Сообщение.ДобавитьСтроку("Некий текст");
+
+ // Отправим сообщение всем клиентам, слушающим топик.
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
+КонецПроцедуры
+
+```
+
+Пример клиента который подписывается на топик, и вызывает события в зависимости от типа полученного сообщения :
+
+```js
+eventSource = new EventSource('sse/acorndiscussion');
+
+eventSource.addEventListener('like', function(e) {
+likeElem.innerHTML = e.data;
+});
+
+eventSource.addEventListener('watch', function(e) {
+watchElem.innerHTML = e.data;
+});
+
+eventSource.addEventListener('newComment', function(e) {
+addComment(e.data);
+});
+
+```
+
+Полный пример можно посмотреть примерах - [контрол](example/hwapp/СерверныеСобытия.os) и [клиент](example/hwapp/view/main_sse.html).
+
+Апи объектов:
+
+```БрокерСообщенийВебСокетов```
+
+- ОтправитьСообщениеВсем(Топик, Сообщение): Отправка сообщения всем;
+- ОтправитьСообщениеПоИдСоединения(ИдСоединения, Сообщение): Отправка сообщения конкретному клиенту;
+- ОтправитьСообщениеСписку(Топик, Сообщение, МассивИдентификаторов): Отправка сообщения массиву клиентов;
+- ОтправитьСообщениеВсемКроме(Топик, Сообщение, МассивИсключенийИдентификаторов): Отправка сообщения с исключающим массивом клиентов;
+
+```ТопикиСерверныхСобытий```
+
+- Существует(Топик): Проверка существования топика;
+- Добавить(Топик, ОбработчикОткрытия, ОбработчикЗакрытия): Добавление топика. С возможностью подписки на события открытия и закрытия соединения. Тут принимаются объекты Действие, которые должны иметь интрефейс: ```Процедура ИмяОбработчика(Сессия, ИД) Экспорт``` Где сессия - идентификатор сессии, и идентифкатор конкретного соединения.
+
+```Сообщение```. Получается из фабрики ответов. ```Сообщение = ФабрикаОтветов.СерверноеСобытие();```
+
+- ТипСобытия(ТипСобытия): Установка типа события;
+- ДобавитьСтроку(Строка): Добавление строки в сообщение;
+- Идентификатор(ид) : Установка идентификатора сообщения;
+
# Использование cli
winow предоставляет интерфейс командной строки. Запуск приложения становится еще проще. Для этого нужно установить пакет [winow-cli](https://github.com/autumn-library/winow-cli).
diff --git a/example/hwapp/view/main_sse.html b/example/hwapp/view/main_sse.html
new file mode 100644
index 0000000..c8ef6a0
--- /dev/null
+++ b/example/hwapp/view/main_sse.html
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+Сейчас смотрят: 0
+
+
\ No newline at end of file
diff --git "a/example/hwapp/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\320\265\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os" "b/example/hwapp/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\320\265\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os"
new file mode 100644
index 0000000..9c136a1
--- /dev/null
+++ "b/example/hwapp/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\320\265\320\241\320\276\320\261\321\213\321\202\320\270\321\217.os"
@@ -0,0 +1,110 @@
+
+&Пластилин Перем БрокерСообщенийСобытийСервера;
+&пластилин Перем ФабрикаОтветов;
+
+Перем КоличествоЛайков;
+Перем ИмяТопика;
+Перем СейчасСмотрят;
+Перем Комментарии;
+
+&Контроллер("/sse")
+&Отображение(Шаблон = "./hwapp/view/main_sse.html")
+Процедура ПриСозданииОбъекта(&Пластилин ТопикиСерверныхСобытий)
+
+ ИмяТопика = "/sse/acorndiscussion";
+ КоличествоЛайков = 3;
+ СейчасСмотрят = 0;
+
+ ТопикиСерверныхСобытий.Добавить(ИмяТопика,
+ Новый Действие(ЭтотОбъект, "НовоеПодключениеССЕ"),
+ Новый Действие(ЭтотОбъект, "ОтключениеССЕ"));
+
+ Комментарии = Новый ТаблицаЗначений();
+ Комментарии.Колонки.Добавить("Имя");
+ Комментарии.Колонки.Добавить("Комментарий");
+ Комментарии.Колонки.Добавить("Дата");
+
+КонецПроцедуры
+
+Процедура НовоеПодключениеССЕ(Сессия, ИД) Экспорт
+ СейчасСмотрят = СейчасСмотрят + 1;
+
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("like")
+ .ДобавитьСтроку(Строка(КоличествоЛайков));
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеПоИдСоединения(ИД, Сообщение);
+
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("watch")
+ .ДобавитьСтроку(Строка(СейчасСмотрят));
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
+
+ Для Каждого Комментарий из Комментарии Цикл
+ Сообщение = СообщениеИзСтрокиКомментария(Комментарий);
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеПоИдСоединения(ИД, Сообщение);
+ КонецЦикла;
+
+КонецПроцедуры
+
+Процедура ОтключениеССЕ(Сессия, ИД) Экспорт
+ СейчасСмотрят = СейчасСмотрят - 1;
+
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("watch")
+ .ДобавитьСтроку(Строка(СейчасСмотрят));
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
+КонецПроцедуры
+
+&ТочкаМаршрута("")
+Процедура ОсновнаяТочка() Экспорт
+
+КонецПроцедуры
+
+&ТочкаМаршрута("postcomment")
+Процедура ЗапоститьКомментарий(ТелоЗапросОбъект) Экспорт
+ ДобавитьКомментарий(ТелоЗапросОбъект.name, ТелоЗапросОбъект.comment);
+КонецПроцедуры
+
+&ТочкаМаршрута("addLike")
+Процедура ПоставитьЛайк() Экспорт
+ КоличествоЛайков = КоличествоЛайков + 1;
+ Попытка
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("like")
+ .ДобавитьСтроку(Строка(КоличествоЛайков));
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
+ Исключение
+ Сообщить(ОписаниеОшибки());
+ КонецПопытки;
+КонецПроцедуры
+
+Процедура ОповеститьОКомментарии(СтрокаКомментария)
+
+ Сообщение = СообщениеИзСтрокиКомментария(СтрокаКомментария);
+
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
+
+КонецПроцедуры
+
+Функция СообщениеИзСтрокиКомментария(СтрокаКомментария)
+ СтрокаШаблон = "";
+ Текст = СтрШаблон(СтрокаШаблон, СтрокаКомментария.Имя, СтрокаКомментария.Дата, СтрокаКомментария.Комментарий);
+
+ Сообщение = ФабрикаОтветов.СерверноеСобытие();
+ Сообщение.ТипСобытия("newComment")
+ .ДобавитьСтроку(Текст);
+
+ Возврат Сообщение;
+КонецФункции
+
+Процедура ДобавитьКомментарий(Имя, Комментарий)
+
+ НовыйКомментарий = Комментарии.Добавить();
+ НовыйКомментарий.Имя = Имя;
+ НовыйКомментарий.Комментарий = Комментарий;
+ НовыйКомментарий.Дата = ТекущаяДата();
+
+ ОповеститьОКомментарии(НовыйКомментарий);
+
+КонецПроцедуры
\ No newline at end of file
diff --git a/packagedef b/packagedef
index 543a342..1fafad8 100644
--- a/packagedef
+++ b/packagedef
@@ -72,7 +72,7 @@
Описание.Имя("winow")
- .Версия("0.7.0")
+ .Версия("0.8.0")
.Автор("Никита Иванченко")
.АдресАвтора("https://github.com/Nivanchenko")
.Описание("Минималистичный веб-сервер на нативном OneScript")
@@ -83,9 +83,10 @@
.ВключитьФайл("README.md")
.ВключитьФайл("package-loader.os")
.ЗависитОт("asserts", "1.4.0")
- .ЗависитОт("autumn", "4.2.0")
+ .ЗависитОт("autumn", "4.2.1")
.ЗависитОт("json")
.ЗависитОт("autumn-logos", "1.2.0")
+ .ЗависитОт("semaphore", "1.1.0")
.ЗависитОт("fs")
.РазработкаЗависитОт("1commands")
.РазработкаЗависитОт("1testrunner")
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\221\321\200\320\276\320\272\320\265\321\200\320\241\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\271\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\260.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\221\321\200\320\276\320\272\320\265\321\200\320\241\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\271\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\260.os"
new file mode 100644
index 0000000..61acfd4
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\221\321\200\320\276\320\272\320\265\321\200\320\241\320\276\320\276\320\261\321\211\320\265\320\275\320\270\320\271\320\241\320\276\320\261\321\213\321\202\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\260.os"
@@ -0,0 +1,82 @@
+
+&Пластилин Перем СоединенияСерверныхСобытий;
+&Пластилин Перем ТопикиСерверныхСобытий;
+
+&Желудь
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+Функция КоличествоСоединений(Топик) Экспорт
+ Возврат СоединенияСерверныхСобытий.Количество(Топик);
+КонецФункции
+
+Процедура ОтправитьСообщениеВсем(Топик, Сообщение) Экспорт
+ ПроверитьТопик(Топик);
+
+ Соединения = СоединенияСерверныхСобытий.ПолучитьСоединенияПоТопику(Топик);
+
+ ОтправитьСообщениеКлиентам(Соединения, Сообщение);
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщениеПоИдСоединения(ИдСоединения, Сообщение) Экспорт
+
+ Соединения = СоединенияСерверныхСобытий.ПолучитьСоединениеПоИдСоединения(ИдСоединения);
+
+ ОтправитьСообщениеКлиентам(Соединения, Сообщение);
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщениеСписку(Топик, Сообщение, СписокИдентификаторов) Экспорт
+ ПроверитьТопик(Топик);
+
+ Соединения = СоединенияСерверныхСобытий.ПолучитьСоединенияПоТопикуСпискуИдентификаторов(Топик, СписокИдентификаторов);
+
+ ОтправитьСообщениеКлиентам(Соединения, Сообщение);
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщениеВсемКроме(Топик, Сообщение, СписокИсключенийИдентификаторов) Экспорт
+ ПроверитьТопик(Топик);
+
+ Соединения = СоединенияСерверныхСобытий.ПолучитьСоединенияПоТопикуКромеСпискаИдентификаторов(Топик, СписокИсключенийИдентификаторов);
+
+ ОтправитьСообщениеКлиентам(Соединения, Сообщение);
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщение(Идентификатор, Топик, Сообщение) Экспорт
+ ПроверитьТопик(Топик);
+
+ Соединения = СоединенияСерверныхСобытий.ПолучитьСоединениеПоИдентификатору(Топик, Идентификатор);
+
+ ОтправитьСообщениеКлиентам(Соединения, Сообщение);
+
+КонецПроцедуры
+
+Процедура ОтправитьСообщениеКлиентам(Соединения, Сообщение)
+
+ ДДСобщения = ПолучитьДвоичныеДанныеИзСтроки(Сообщение.Сформировать());
+
+ Для Каждого Соединение из Соединения Цикл
+ ОтправитьВПопытке(Соединение.Соединение, ДДСобщения);
+ КонецЦикла;
+
+КонецПроцедуры
+
+Функция ОтправитьВПопытке(Соединение, ДвоичныеДанныеОтвета) Экспорт
+ Попытка
+ Соединение.ОтправитьДвоичныеДанные(ДвоичныеДанныеОтвета);
+ Возврат Истина;
+ Исключение
+ Возврат Ложь;
+ КонецПопытки;
+КонецФункции
+
+Процедура ПроверитьТопик(Топик)
+ Если Не ТопикиСерверныхСобытий.Существует(Топик) Тогда
+ ВызватьИсключение СтрШаблон("Топик %1 не зарегистрирован", Топик);
+ КонецЕсли;
+КонецПроцедуры
+
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\321\205\320\276\320\264\321\217\321\211\320\270\320\271\320\227\320\260\320\277\321\200\320\276\321\201.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\321\205\320\276\320\264\321\217\321\211\320\270\320\271\320\227\320\260\320\277\321\200\320\276\321\201.os"
index 4d603ef..ac9aebf 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\321\205\320\276\320\264\321\217\321\211\320\270\320\271\320\227\320\260\320\277\321\200\320\276\321\201.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\321\205\320\276\320\264\321\217\321\211\320\270\320\271\320\227\320\260\320\277\321\200\320\276\321\201.os"
@@ -154,7 +154,8 @@
Результат = Неопределено;
Если ЗначениеЗаполнено(Тело)
- И СокрЛП(Заголовки["Content-Type"]) = "application/json" Тогда
+ И
+ СтрНайти(НРег(СокрЛП(Заголовки["Content-Type"])), "application/json") > 0 Тогда
Парсер = Новый ПарсерJSON();
Результат = Парсер.ПрочитатьJSON(Тело, Истина, Ложь, Настройки.АвтоматическиПриводитьОбъектыКСтруктуре);
@@ -167,4 +168,9 @@
Функция ЭтоЗапросНаВебСокет() Экспорт
КлючРукопожатия = Заголовки["Sec-WebSocket-Key"];
Возврат НЕ КлючРукопожатия = Неопределено И НЕ ПустаяСтрока(КлючРукопожатия);
+КонецФункции
+
+Функция ЭтоЗапросНаСерверныеСобытия() Экспорт
+ Возврат СокрЛП(Заголовки["Accept"]) = "text/event-stream"
+ И СокрЛП(Заголовки["Connection"]) = "keep-alive";
КонецФункции
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\321\214\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\321\214\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
new file mode 100644
index 0000000..1943de1
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\321\214\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
@@ -0,0 +1,68 @@
+
+Перем ИнтервалПроверки;
+Перем Сообщение;
+Перем Проверять;
+
+&Пластилин
+Перем СоединенияСерверныхСобытий;
+&Пластилин
+Перем БрокерСообщенийСобытийСервера;
+
+&Желудь
+Процедура ПриСозданииОбъекта(&Пластилин Настройки, &Пластилин ФабрикаОтветов)
+ ИнтервалПроверки = Настройки.ИнтервалПроверкиСоединенийСерверныхСобытий;
+
+ Сообщение = ПолучитьДвоичныеДанныеИзСтроки(ФабрикаОтветов.СерверноеСобытие()
+ .ТипСобытия("ping")
+ .ДобавитьСтроку("")
+ .Сформировать());
+
+КонецПроцедуры
+
+Процедура Старт() Экспорт
+
+ Если Проверять = Истина Тогда
+ Возврат;
+ КонецЕсли;
+
+ Проверять = Истина;
+ ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПроверятьВЦикле");
+КонецПроцедуры
+
+Процедура Стоп() Экспорт
+ Проверять = Ложь;
+КонецПроцедуры
+
+Процедура ПроверятьВЦикле() Экспорт
+
+ Пока Проверять Цикл
+ Приостановить(ИнтервалПроверки);
+ Попытка
+ НайтиИУдалитьСоединения();
+ Исключение
+ Сообщить("Что-то пошло нетак в проверке соединений SSE " + ОписаниеОшибки());
+ КонецПопытки;
+
+ КонецЦикла;
+
+КонецПроцедуры
+
+Процедура НайтиИУдалитьСоединения()
+
+ ПулСоеднинений = СоединенияСерверныхСобытий.ПулСоединений();
+ ИдСоединенийКУдалению = Новый Массив();
+ Для Каждого СтрокаСоединения из ПулСоеднинений Цикл
+ Если НЕ СоединениеАктивно(СтрокаСоединения.Соединение) Тогда
+ ИдСоединенийКУдалению.Добавить(СтрокаСоединения.ИдСоединения);
+ КонецЕсли;
+ КонецЦикла;
+
+ Для Каждого ИдСоединения из ИдСоединенийКУдалению Цикл
+ СоединенияСерверныхСобытий.УдалитьСтрокуПулаПоИдСоединения(ИдСоединения);
+ КонецЦикла;
+
+КонецПроцедуры
+
+Функция СоединениеАктивно(Соединение)
+ Возврат БрокерСообщенийСобытийСервера.ОтправитьВПопытке(Соединение, Сообщение);
+КонецФункции
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\222\321\205\320\276\320\264\321\217\321\211\320\270\321\205\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\222\321\205\320\276\320\264\321\217\321\211\320\270\321\205\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
index 89f7f96..a3fe98f 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\222\321\205\320\276\320\264\321\217\321\211\320\270\321\205\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\234\320\265\320\275\320\265\320\264\320\266\320\265\321\200\320\222\321\205\320\276\320\264\321\217\321\211\320\270\321\205\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
@@ -2,6 +2,7 @@
&Пластилин Перем Настройки;
&Пластилин Перем ПарсерЗапросов;
&Пластилин Перем СоединенияВебСокетов;
+&Пластилин Перем СоединенияСерверныхСобытий;
&Желудь
Процедура ПриСозданииОбъекта()
@@ -13,13 +14,19 @@
Результат = ПарсерЗапросов.ОбработатьДвоичныеДанные(ДвоичныеДанныеЗапроса);
Если Результат.ЗакрыватьСоединение = Ложь Тогда
- СохранитьСоединение(Соединение, Результат.Идентификатор, Результат.Топик);
+ СохранитьСоединение(Соединение, Результат.ТипСоединения, Результат.Идентификатор, Результат.Топик);
КонецЕсли;
Возврат Результат;
КонецФункции
-Процедура СохранитьСоединение(Соединение, Идентификатор, Топик)
- СоединенияВебСокетов.Добавить(Соединение, Идентификатор, Топик);
+Процедура СохранитьСоединение(Соединение, ТипСоединения, Идентификатор, Топик)
+
+ Если ТипСоединения = "ВС" Тогда
+ СоединенияВебСокетов.Добавить(Соединение, Идентификатор, Топик);
+ ИначеЕсли ТипСоединения = "СС" Тогда
+ СоединенияСерверныхСобытий.Добавить(Соединение, Идентификатор, Топик);
+ КонецЕсли;
+
КонецПроцедуры
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270.os"
index 4d2c4cd..ff669f0 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\235\320\260\321\201\321\202\321\200\320\276\320\271\320\272\320\270.os"
@@ -5,6 +5,9 @@
&Деталька(Значение = "winow.ИмяХоста", ЗначениеПоУмолчанию = "localhost")
Перем ИмяХоста Экспорт;
+&Деталька(Значение = "winow.ИнтервалПроверкиСоединенийСерверныхСобытий", ЗначениеПоУмолчанию = 5000)
+Перем ИнтервалПроверкиСоединенийСерверныхСобытий Экспорт;
+
&Деталька(Значение = "winow.КаталогСПриложениями", ЗначениеПоУмолчанию = "./app")
Перем КаталогСПриложениями Экспорт;
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
index 43beae1..bb06da9 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
@@ -5,7 +5,6 @@
&Пластилин Перем МенеджерДоступа;
&Пластилин Перем Поделка;
&Пластилин Перем КонструкторыПараметров;
-&Пластилин Перем Настройки;
&ЛогВебСервера
Перем Лог;
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
index 8d6e7f5..3fdd77e 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\261\321\200\320\260\320\261\320\276\321\202\321\207\320\270\320\272\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\320\271.os"
@@ -1,12 +1,16 @@
&Пластилин Перем Настройки;
&Пластилин Перем МенеджерВходящихСоединений;
+Перем ТаймаутЧтения;
+Перем РазмерБуфера;
+
&ЛогВебСервера
Перем Лог;
&Желудь
Процедура ПриСозданииОбъекта()
-
+ ТаймаутЧтения = 10;
+ РазмерБуфера = 1024;
КонецПроцедуры
Процедура Обработать(Соединение) Экспорт
@@ -37,12 +41,30 @@
Приостановить(Настройки.ЗадержкаПередЧтениемСокета);
КонецЕсли;
- ДанныеЗапроса = Соединение.ПрочитатьДвоичныеДанные();
+ ДанныеЗапроса = ВычитатьВПопыткеДвоичныеДанные(Соединение);
Возврат ДанныеЗапроса;
КонецФункции
-Процедура ОтправитьВПопытке(Соединение, ДвоичныеДанныеОтвета)
+Функция ВычитатьВПопыткеДвоичныеДанные(Соединение)
+
+ Данные = Новый Массив();
+
+ Попытка
+ Пока Истина Цикл
+ Соединение.ТаймаутЧтения = ТаймаутЧтения;
+ ДанныеСоединения = Соединение.ПрочитатьДвоичныеДанные(РазмерБуфера);
+ Данные.Добавить(ДанныеСоединения);
+ КонецЦикла;
+ Исключение
+
+ КонецПопытки;
+
+ Возврат СоединитьДвоичныеДанные(Данные);
+
+КонецФункции
+
+Процедура ОтправитьВПопытке(Соединение, ДвоичныеДанныеОтвета) Экспорт
Попытка
Соединение.ОтправитьДвоичныеДанные(ДвоичныеДанныеОтвета);
Исключение
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\321\201\320\265\321\200\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\321\201\320\265\321\200\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
index a79aa35..a3484f1 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\321\201\320\265\321\200\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\260\321\200\321\201\320\265\321\200\320\227\320\260\320\277\321\200\320\276\321\201\320\276\320\262.os"
@@ -7,6 +7,7 @@
&Пластилин Перем ПодготовительОтветов;
&Пластилин Перем МенеджерСессий;
&Пластилин Перем ФабрикаОтветов;
+&Пластилин Перем ТопикиСерверныхСобытий;
&Желудь
Процедура ПриСозданииОбъекта(
@@ -30,11 +31,29 @@
Результат = СтруктураРезультатаПарсинга();
Если Запрос.ЭтоЗапросНаВебСокет() Тогда
+
+ Результат.ТипСоединения = "ВС";
+
Ответ = ФабрикаОтветов.ОтветРукопожатияВебСокета(Запрос);
-
+
Результат.Идентификатор = Запрос.Сессия.Идентификатор();
Результат.ЗакрыватьСоединение = Ложь;
Результат.Топик = Запрос.Путь;
+
+ ИначеЕсли Запрос.ЭтоЗапросНаСерверныеСобытия() Тогда
+
+ Результат.ТипСоединения = "СС";
+ Если ТопикиСерверныхСобытий.Существует(Запрос.Путь) = Ложь Тогда
+ Ответ = ФабрикаОтветов.НовыйОтвет404();
+ Иначе
+ Результат.Идентификатор = Запрос.Сессия.Идентификатор();
+ Результат.ЗакрыватьСоединение = Ложь;
+ Результат.Топик = Запрос.Путь;
+ Ответ = ФабрикаОтветов.ПустойОтвет();
+ Ответ.УстановитьСостояниеОК();
+ Ответ.Заголовки["Content-Type"] = "text/event-stream; charset=utf-8";
+ КонецЕсли;
+
Иначе
Ответ = ОбработчикЗапросов.СформироватьОтвет(Запрос);
КонецЕсли;
@@ -118,5 +137,5 @@
КонецПроцедуры
Функция СтруктураРезультатаПарсинга()
- Возврат Новый Структура("ЗакрыватьСоединение, Ответ, Идентификатор, Топик", Истина);
+ Возврат Новый Структура("ЗакрыватьСоединение, Ответ, Идентификатор, Топик, ТипСоединения", Истина);
КонецФункции
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\277\320\270\321\201\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\277\320\270\321\201\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
new file mode 100644
index 0000000..dc564df
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\277\320\270\321\201\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
@@ -0,0 +1,56 @@
+
+Перем ПодписчикиОткрытия;
+Перем ПодписчикиЗакрытия;
+
+&Желудь
+Процедура ПриСозданииОбъекта()
+ Инициализация();
+КонецПроцедуры
+
+Процедура СобытиеОткрытия(Топик, Идентификатор, ИдСоединения) Экспорт
+ Для Каждого Подписчики из ПодписчикиОткрытия.НайтиСтроки(Новый Структура("Топик", Топик)) Цикл
+ ВыполнитьОбработчикФоном(Подписчики.Обработчик, Идентификатор, ИдСоединения);
+ КонецЦикла;
+КонецПроцедуры
+
+Процедура СобытиеЗакрытия(Топик, Идентификатор, ИдСоединения) Экспорт
+ Для Каждого Подписчики из ПодписчикиЗакрытия.НайтиСтроки(Новый Структура("Топик", Топик)) Цикл
+ ВыполнитьОбработчикФоном(Подписчики.Обработчик, Идентификатор, ИдСоединения);
+ КонецЦикла;
+КонецПроцедуры
+
+Процедура ВыполнитьОбработчикФоном(Обработчик, Идентификатор, ИдСоединения)
+ Параметры = Новый Массив();
+ Параметры.Добавить(Обработчик);
+ Параметры.Добавить(Идентификатор);
+ Параметры.Добавить(ИдСоединения);
+ ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЗапуститьФоновоеЗаданиеОбработчика", Параметры);
+КонецПроцедуры
+
+Процедура ЗапуститьФоновоеЗаданиеОбработчика(Обработчик, Идентификатор, ИдСоединения) Экспорт
+ Приостановить(50);
+ Обработчик.Выполнить(Идентификатор, ИдСоединения);
+КонецПроцедуры
+
+Процедура ПодпискаНаОткрытие(Топик, Обработчик) Экспорт
+ НоваяПодписка = ПодписчикиОткрытия.Добавить();
+ НоваяПодписка.Топик = Топик;
+ НоваяПодписка.Обработчик = Обработчик;
+КонецПроцедуры
+
+Процедура ПодпискаНаЗакрытие(Топик, Обработчик) Экспорт
+ НоваяПодписка = ПодписчикиЗакрытия.Добавить();
+ НоваяПодписка.Топик = Топик;
+ НоваяПодписка.Обработчик = Обработчик;
+КонецПроцедуры
+
+Процедура Инициализация()
+ ПодписчикиОткрытия = Новый ТаблицаЗначений();
+ ПодписчикиОткрытия.Колонки.Добавить("Топик");
+ ПодписчикиОткрытия.Колонки.Добавить("Обработчик");
+
+ ПодписчикиЗакрытия = Новый ТаблицаЗначений();
+ ПодписчикиЗакрытия.Колонки.Добавить("Топик");
+ ПодписчикиЗакрытия.Колонки.Добавить("Обработчик");
+КонецПроцедуры
+
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\200\320\270\320\272\320\273\320\260\320\264\320\275\320\276\320\271\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os"
similarity index 56%
rename from "src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os"
rename to "src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\200\320\270\320\272\320\273\320\260\320\264\320\275\320\276\320\271\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os"
index a8af4c8..b97a706 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\321\200\320\270\320\272\320\273\320\260\320\264\320\275\320\276\320\271\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200.os"
@@ -1,7 +1,7 @@
-&Пластилин Перем ОбработчикСоединений;
-&Пластилин Перем Настройки;
-&Пластилин Перем СлушательПорта;
+&Пластилин Перем ОбработчикСоединений;
+&Пластилин Перем СлушательПорта;
+&Пластилин Перем КонтрольСоединенийСерверныхСобытий;
&Желудь
Процедура ПриСозданииОбъекта()
@@ -10,6 +10,8 @@
Процедура Старт() Экспорт
+ КонтрольСоединенийСерверныхСобытий.Старт();
+
СлушательПорта.Запустить();
Пока СлушательПорта.Активен() Цикл
@@ -21,6 +23,12 @@
ФоновыеЗадания.Выполнить(ОбработчикСоединений, "Обработать", МассивПараметров);
- КонецЦикла
+ КонецЦикла;
+
+ КонтрольСоединенийСерверныхСобытий.Стоп();
+КонецПроцедуры
+
+Процедура Стоп() Экспорт
+ СлушательПорта.Остановить();
КонецПроцедуры
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\320\276\320\265\320\241\320\276\320\261\321\213\321\202\320\270\320\265.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\320\276\320\265\320\241\320\276\320\261\321\213\321\202\320\270\320\265.os"
new file mode 100644
index 0000000..9a2a454
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\265\321\200\320\262\320\265\321\200\320\275\320\276\320\265\320\241\320\276\320\261\321\213\321\202\320\270\320\265.os"
@@ -0,0 +1,50 @@
+Перем ТипСобытия;
+Перем Идентификатор;
+Перем Данные;
+
+Процедура ПриСозданииОбъекта()
+ ТипСобытия = "";
+ Идентификатор = "";
+ Данные = Новый Массив();
+КонецПроцедуры
+
+Функция ТипСобытия(ТипПередаваемогоСобытия) Экспорт
+ ТипСобытия = ТипПередаваемогоСобытия;
+ Возврат ЭтотОбъект;
+КонецФункции
+
+Функция Идентификатор(Ид) Экспорт
+ Идентификатор = Ид;
+ Возврат ЭтотОбъект;
+КонецФункции
+
+Функция ДобавитьСтроку(Строка) Экспорт
+
+ Для Каждого ТекСтрока из СтрРазделить(Строка, Символы.ПС) Цикл
+ Данные.Добавить("data: " + ТекСтрока);
+ КонецЦикла;
+
+ Возврат ЭтотОбъект;
+КонецФункции
+
+Функция Сформировать() Экспорт
+
+ Результат = Новый Массив();
+
+ Если ЗначениеЗаполнено(ТипСобытия) Тогда
+ Результат.Добавить("event: " + ТипСобытия);
+ КонецЕсли;
+
+ Для Каждого СтрокаДанных Из Данные Цикл
+ Результат.Добавить(СтрокаДанных);
+ КонецЦикла;
+
+ Если ЗначениеЗаполнено(Идентификатор) Тогда
+ Результат.Добавить("id: " + Идентификатор);
+ КонецЕсли;
+
+ Результат.Добавить(Символы.ПС);
+
+ Возврат СтрСоединить(Результат, Символы.ПС);
+
+КонецФункции
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
new file mode 100644
index 0000000..ef24887
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\241\320\276\320\265\320\264\320\270\320\275\320\265\320\275\320\270\321\217\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
@@ -0,0 +1,141 @@
+#Использовать semaphore
+
+Перем ПулСоединений;
+Перем Подписки;
+Перем Семафор;
+
+&Желудь
+Процедура ПриСозданииОбъекта(&Пластилин ПодпискиСерверныхСобытий)
+ Подписки = ПодпискиСерверныхСобытий;
+ Инициализация();
+КонецПроцедуры
+
+Функция Количество(Топик) Экспорт
+ Возврат ПолучитьСоединенияПоТопику(Топик).Количество();
+КонецФункции
+
+Функция ПулСоединений() Экспорт
+ Возврат ПулСоединений.Скопировать(,"Соединение, ИдСоединения");
+КонецФункции
+
+Процедура Добавить(Соединение, Идентификатор, Топик) Экспорт
+
+ Семафор.Захватить();
+
+ НовоеСоединение = ПулСоединений.Добавить();
+ НовоеСоединение.Идентификатор = Идентификатор;
+ НовоеСоединение.Топик = Топик;
+ НовоеСоединение.Соединение = Соединение;
+ НовоеСоединение.ИдСоединения = Строка(Новый УникальныйИдентификатор());
+
+ Семафор.Освободить();
+
+ ОповеститьОткрытиеСоединения(Идентификатор, Топик, НовоеСоединение.ИдСоединения);
+
+КонецПроцедуры
+
+Процедура Удалить(Идентификатор, Топик) Экспорт
+ Содединения = ПолучитьСоединениеПоИдентификатору(Топик, Идентификатор);
+ Для Каждого СтрокаСоединения из Содединения Цикл
+ УдалитьСтрокуПула(СтрокаСоединения);
+ КонецЦикла;
+КонецПроцедуры
+
+Процедура УдалитьСтрокуПулаПоИдСоединения(ИдСоединения) Экспорт
+ Соединения = ПолучитьСоединениеПоИдСоединения(ИдСоединения);
+
+ Если Соединения.Количество() > 0 Тогда
+ УдалитьСтрокуПула(Соединения[0]);
+ КонецЕсли;
+КонецПроцедуры
+
+Процедура УдалитьСтрокуПула(СтрокаСоединения) Экспорт
+
+ Семафор.Захватить();
+
+ Идентификатор = СтрокаСоединения.Идентификатор;
+ Топик = СтрокаСоединения.Топик;
+ ИдСоединения = СтрокаСоединения.ИдСоединения;
+
+ ЗакрытьСоединениеВПопытке(СтрокаСоединения.Соединение);
+ ОсвободитьОбъект(СтрокаСоединения.Соединение);
+ ПулСоединений.Удалить(СтрокаСоединения);
+
+ Семафор.Освободить();
+
+ ОповеститьЗакрытиеСоединения(Идентификатор, Топик, ИдСоединения);
+КонецПроцедуры
+
+Функция ПолучитьСоединенияПоТопику(Топик) Экспорт
+ Возврат ПолучитьСоединенияСОтбором(Новый Структура("Топик", Топик));
+КонецФункции
+
+Функция ПолучитьСоединенияПоТопикуСпискуИдентификаторов(Топик, СписокИдентификаторов) Экспорт
+ Соединения = ПолучитьСоединенияПоТопику(Топик);
+ Результат = Новый Массив();
+ Для Каждого ДанныеСоединения из Соединения Цикл
+ Если НЕ СписокИдентификаторов.Найти(ДанныеСоединения.ИдСоединения) = Неопределено Тогда
+ Результат.Добавить(ДанныеСоединения);
+ КонецЕсли;
+ КонецЦикла;
+ Возврат Результат;
+КонецФункции
+
+Функция ПолучитьСоединенияПоТопикуКромеСпискаИдентификаторов(Топик, СписокИсключенийИдентификаторов) Экспорт
+ Соединения = ПолучитьСоединенияПоТопику(Топик);
+ Результат = Новый Массив();
+ Для Каждого ДанныеСоединения из Соединения Цикл
+ Если СписокИсключенийИдентификаторов.Найти(ДанныеСоединения.ИдСоединения) = Неопределено Тогда
+ Результат.Добавить(ДанныеСоединения);
+ КонецЕсли;
+ КонецЦикла;
+ Возврат Результат;
+КонецФункции
+
+Функция ПолучитьСоединениеПоИдентификатору(Топик, Идентификатор) Экспорт
+
+ Возврат ПолучитьСоединенияСОтбором(Новый Структура("Топик, Идентификатор", Топик, Идентификатор));
+
+КонецФункции
+
+Функция ПолучитьСоединениеПоИдСоединения(ИдСоединения) Экспорт
+
+ Возврат ПолучитьСоединенияСОтбором(Новый Структура("ИдСоединения", ИдСоединения));
+
+КонецФункции
+
+Функция ПолучитьСоединенияСОтбором(Отбор) Экспорт
+ Возврат ПулСоединений.НайтиСтроки(Отбор);
+КонецФункции
+
+Процедура Инициализация()
+ Семафор = Новый Семафор();
+ ПулСоединений = Новый ТаблицаЗначений();
+ ПулСоединений.Колонки.Добавить("Идентификатор");
+ ПулСоединений.Колонки.Добавить("Топик");
+ ПулСоединений.Колонки.Добавить("Соединение");
+ ПулСоединений.Колонки.Добавить("ИдСоединения");
+
+ // TODO: В 2.0 не работают индексы. Ждем фикса
+ // ПулСоединений.Индексы.Добавить("Топик");
+ // ПулСоединений.Индексы.Добавить("ИдСоединения");
+ // ПулСоединений.Индексы.Добавить("Идентификатор");
+ // ПулСоединений.Индексы.Добавить("Топик, Идентификатор");
+
+КонецПроцедуры
+
+Процедура ЗакрытьСоединениеВПопытке(Соединение) Экспорт
+ Попытка
+ Соединение.Закрыть();
+ Исключение
+ КонецПопытки;
+КонецПроцедуры
+
+Процедура ОповеститьЗакрытиеСоединения(Идентификатор, Топик, ИдСоединения)
+ Подписки.СобытиеЗакрытия(Топик, Идентификатор, ИдСоединения);
+КонецПроцедуры
+
+Процедура ОповеститьОткрытиеСоединения(Идентификатор, Топик, ИдСоединения)
+ Подписки.СобытиеОткрытия(Топик, Идентификатор, ИдСоединения);
+КонецПроцедуры
+
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\242\320\276\320\277\320\270\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\242\320\276\320\277\320\270\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
new file mode 100644
index 0000000..55d817b
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\242\320\276\320\277\320\270\320\272\320\270\320\241\320\265\321\200\320\262\320\265\321\200\320\275\321\213\321\205\320\241\320\276\320\261\321\213\321\202\320\270\320\271.os"
@@ -0,0 +1,30 @@
+
+Перем Топики;
+
+&Пластилин
+Перем ПодпискиСерверныхСобытий;
+
+&Желудь
+Процедура ПриСозданииОбъекта()
+ Топики = Новый Соответствие();
+КонецПроцедуры
+
+Процедура Добавить(Топик, ОбработчикПодключения = Неопределено, ОбработчикОтключения = Неопределено) Экспорт
+
+ Топики.Вставить(Топик, Истина);
+
+ Если не ОбработчикПодключения = Неопределено Тогда
+ ПодпискиСерверныхСобытий.ПодпискаНаОткрытие(Топик, ОбработчикПодключения);
+ КонецЕсли;
+
+ Если не ОбработчикОтключения = Неопределено Тогда
+ ПодпискиСерверныхСобытий.ПодпискаНаЗакрытие(Топик, ОбработчикОтключения);
+ КонецЕсли;
+
+КонецПроцедуры
+
+Функция Существует(Топик) Экспорт
+
+ Возврат Топики.Получить(Топик) <> Неопределено;
+
+КонецФункции
\ No newline at end of file
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200\320\276\320\262.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200\320\276\320\262.os"
new file mode 100644
index 0000000..55cfb01
--- /dev/null
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\222\320\265\320\261\320\241\320\265\321\200\320\262\320\265\321\200\320\276\320\262.os"
@@ -0,0 +1,37 @@
+
+&ЛогВебСервера
+Перем Лог;
+
+&Пластилин
+Перем ПрикладнойВебСервер;
+Перем НативныйВебСервер;
+
+Перем ИспользоватьНативныйСервер;
+
+
+&Дуб
+Процедура ПриСозданииОбъекта()
+ // TODO: Поддержка нативного веб-сервера
+ ИспользоватьНативныйСервер = Ложь;
+КонецПроцедуры
+
+
+&Завязь(Тип = "Строка")
+Функция ВебСервер() Экспорт
+
+ Если ИспользоватьНативныйСервер Тогда
+
+ // TODO: Поддержка нативного веб-сервера
+
+ ВызватьИсключение "Нативный сервер в разработке";
+
+ // BSLLS:UnreachableCode-off
+ Лог.Информация("Используется нативный веб-сервер");
+ Возврат НативныйВебСервер;
+
+ Иначе
+ Лог.Информация("Используется прикладной веб-сервер");
+ Возврат ПрикладнойВебСервер;
+ КонецЕсли;
+КонецФункции
+
diff --git "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os" "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os"
index 8d2a5a8..22b6e1d 100644
--- "a/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os"
+++ "b/src/\320\232\320\273\320\260\321\201\321\201\321\213/\320\244\320\260\320\261\321\200\320\270\320\272\320\260\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os"
@@ -13,6 +13,10 @@
МенеджерОтображений = _МенеджерОтображений;
КонецПроцедуры
+Функция СерверноеСобытие() Экспорт
+ Возврат Новый СерверноеСобытие();
+КонецФункции
+
Функция ПустойОтвет() Экспорт
Возврат Поделка.НайтиЖелудь("Ответ");
КонецФункции
@@ -72,4 +76,4 @@
Функция ШаблонОшибки(КодОшибки)
Возврат МенеджерОтображений.ПолучитьОтображение(КодОшибки);
-КонецФункции
\ No newline at end of file
+КонецФункции
diff --git a/tests/alltest.os b/tests/alltest.os
index 68acf1e..76bf4c6 100644
--- a/tests/alltest.os
+++ b/tests/alltest.os
@@ -22,6 +22,7 @@
Приостановить(1000);
Настройки = Поделка.НайтиЖелудь("Настройки");
Настройки.ЗадержкаПередЗакрытиемСокета = 300;
+ Настройки.ИнтервалПроверкиСоединенийСерверныхСобытий = 100;
КонецЕсли;
КонецПроцедуры
@@ -36,6 +37,16 @@
Возврат Ответ;
КонецФункции
+Функция ПроизвольныйГетЗапрос(АдресТеста, Заголовки = Неопределено)
+ Соединение = Новый HTTPСоединение("http://localhost",3333,,,,5);
+ Запрос = Новый HTTPЗапрос(АдресТеста);
+ Если Не Заголовки = Неопределено Тогда
+ Запрос.Заголовки = Заголовки;
+ КонецЕсли;
+ Ответ = Соединение.Получить(Запрос);
+ Возврат Ответ;
+КонецФункции
+
Функция ПостЗапросТелоИзСтроки(АдресТеста, ТелоЗапроса, Заголовки = Неопределено)
Соединение = Новый HTTPСоединение("http://localhost",3333,,,,5);
Запрос = Новый HTTPЗапрос(СтрШаблон("/tests/%1", АдресТеста));
@@ -528,4 +539,143 @@
Ожидаем.Что(Ответ.КодСостояния).Равно(200);
Ожидаем.Что(Ответ.ПолучитьТелоКакСтроку()).Равно("некоторыйпараметр");
+КонецПроцедуры
+
+&Тест
+Процедура ССЕКлиентПодключился() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+
+ // Кода
+
+ // Тогда
+
+ Ожидаем.Что(ССЕКлиент.ПервичныйОтвет()).Содержит("200");
+
+КонецПроцедуры
+
+&Тест
+Процедура ССЕОтправитьВсем() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент1 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+ ССЕКлиент2 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+
+ ТекстСообщения = "Привет";
+
+ // Когда
+ ПроизвольныйГетЗапрос("/sse/sendall/" + ТекстСообщения);
+
+ Приостановить(1000);
+
+ Сообщения1 = ССЕКлиент1.Сообщения();
+ Сообщения2 = ССЕКлиент2.Сообщения();
+
+ // Тогда
+ Ожидаем.Что(Сообщения1.Количество(), "Сообщения1").Равно(1);
+ Ожидаем.Что(Сообщения2.Количество(), "Сообщения2").Равно(1);
+
+ Ожидаем.Что(Сообщения1[0], "Сообщение1").Равно("data: " + ТекстСообщения);
+ Ожидаем.Что(Сообщения2[0], "Сообщение2").Равно("data: " + ТекстСообщения);
+
+КонецПроцедуры
+
+&Тест
+Процедура ССЕОтправитьВсемКастомныйТип() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент1 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+
+ ТекстСообщения = "Привет";
+
+ // Когда
+ ПроизвольныйГетЗапрос("/sse/sendalltype/" + ТекстСообщения);
+
+ Приостановить(1000);
+
+ Сообщения1 = ССЕКлиент1.Сообщения();
+
+ // Тогда
+ Ожидаем.Что(Сообщения1.Количество(), "Сообщения1").Равно(1);
+
+ Ожидаем.Что(Сообщения1[0], "Сообщение1").Равно("event: evnt" + Символы.ПС + "data: " + ТекстСообщения);
+
+КонецПроцедуры
+
+&Тест
+Процедура ССЕОтправитьТолько() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент1 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+ ССЕКлиент2 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+
+ // Когда
+ ПроизвольныйГетЗапрос("/sse/sendbyid/" + КонтролССЕ.ИдПодключений[0]);
+
+ Приостановить(1000);
+
+ Сообщения1 = ССЕКлиент1.Сообщения();
+ Сообщения2 = ССЕКлиент2.Сообщения();
+
+ // Тогда
+ Ожидаем.Что(КонтролССЕ.ИдПодключений.Количество(), "Количество соединений").Равно(2);
+ Ожидаем.Что(Сообщения1.Количество(), "Сообщения1").Равно(1);
+ Ожидаем.Что(Сообщения2.Количество(), "Сообщения2").Равно(0);
+
+ Ожидаем.Что(Сообщения1[0], "Сообщение1").Равно("data: привет");
+
+КонецПроцедуры
+
+&Тест
+Процедура ССЕОтправитьКроме() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент1 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+ ССЕКлиент2 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+ ССЕКлиент3 = Новый ССЕКлиент("localhost", 3333, "/sse/test1");
+
+ // Когда
+ ПроизвольныйГетЗапрос("/sse/sendbut/" + КонтролССЕ.ИдПодключений[0]);
+
+ Приостановить(1000);
+
+ Сообщения1 = ССЕКлиент1.Сообщения();
+ Сообщения2 = ССЕКлиент2.Сообщения();
+ Сообщения3 = ССЕКлиент2.Сообщения();
+
+ // Тогда
+ Ожидаем.Что(КонтролССЕ.ИдПодключений.Количество(), "Количество соединений").Равно(3);
+ Ожидаем.Что(Сообщения1.Количество(), "Сообщения1").Равно(0);
+ Ожидаем.Что(Сообщения2.Количество(), "Сообщения2").Равно(1);
+ Ожидаем.Что(Сообщения3.Количество(), "Сообщения3").Равно(1);
+
+ Ожидаем.Что(Сообщения2[0], "Сообщение2").Равно("data: привет");
+ Ожидаем.Что(Сообщения3[0], "Сообщение3").Равно("data: привет");
+
+КонецПроцедуры
+
+&Тест
+Процедура ССЕПодпискаНаЗакрытие() Экспорт
+ // Дано
+ КонтролССЕ = Поделка.НайтиЖелудь("КонтролССЕ");
+ КонтролССЕ.ИнициализироватьСостояния();
+ ССЕКлиент1 = Новый ССЕКлиент("localhost", 3333, "/sse/testsub");
+ ССЕКлиент2 = Новый ССЕКлиент("localhost", 3333, "/sse/testsub");
+ ССЕКлиент3 = Новый ССЕКлиент("localhost", 3333, "/sse/testsub");
+
+ // Когда
+
+ ССЕКлиент1.Остановить();
+
+ Приостановить(20000);
+
+ // Тогда
+ Ожидаем.Что(КонтролССЕ.КоличествоЗакрытыхСоединений, "Количество закрытых соединений").Равно(1);
+
КонецПроцедуры
\ No newline at end of file
diff --git "a/tests/app/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\241\320\241\320\225.os" "b/tests/app/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\241\320\241\320\225.os"
new file mode 100644
index 0000000..6521ce7
--- /dev/null
+++ "b/tests/app/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\241\320\241\320\225.os"
@@ -0,0 +1,84 @@
+
+&Пластилин Перем БрокерСообщенийСобытийСервера;
+&пластилин Перем ФабрикаОтветов;
+
+Перем ИмяТопика;
+Перем Лог;
+
+Перем ИдПодключений Экспорт;
+Перем КоличествоЗакрытыхСоединений Экспорт;
+
+&Контроллер("/sse")
+Процедура ПриСозданииОбъекта(&Пластилин ТопикиСерверныхСобытий)
+
+ Лог = Логирование.ПолучитьЛог("oscript.app.winow.test.ssecontrol");
+
+ ИмяТопика = "/sse/test1";
+ ТопикиСерверныхСобытий.Добавить(ИмяТопика, Новый Действие(ЭтотОбъект, "НовоеПодключениеССЕ"));
+
+ ТопикиСерверныхСобытий.Добавить("/sse/testsub",
+ Новый Действие(ЭтотОбъект, "ПодключенияКУдалению"),
+ Новый Действие(ЭтотОбъект, "ОтключениеССЕ"));
+
+ ИнициализироватьСостояния();
+КонецПроцедуры
+
+Процедура ИнициализироватьСостояния() Экспорт
+ ИдПодключений = Новый Массив;
+ КоличествоЗакрытыхСоединений = 0;
+КонецПроцедуры
+
+Процедура НовоеПодключениеССЕ(Сессия, ИД) Экспорт
+
+ ИдПодключений.Добавить(ИД);
+
+КонецПроцедуры
+
+Процедура ПодключенияКУдалению(Сессия, ИД) Экспорт
+
+ Лог.Отладка("Подключился " + Ид);
+
+КонецПроцедуры
+
+Процедура ОтключениеССЕ(Сессия, ИД) Экспорт
+
+ Лог.Отладка("Отключился " + Ид);
+ КоличествоЗакрытыхСоединений = КоличествоЗакрытыхСоединений + 1;
+КонецПроцедуры
+
+&ТочкаМаршрута("")
+Процедура Главная() Экспорт
+
+КонецПроцедуры
+
+&ТочкаМаршрута("sendall/{Текст}")
+Процедура ОтправитьВсем(Текст) Экспорт
+ Событие = ФабрикаОтветов.СерверноеСобытие();
+ Событие.ДобавитьСтроку(Текст);
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Событие);
+КонецПроцедуры
+
+&ТочкаМаршрута("sendalltype/{Текст}")
+Процедура ОтправитьВсемКастомныйТип(Текст) Экспорт
+ Событие = ФабрикаОтветов.СерверноеСобытие();
+ Событие.ТипСобытия("evnt").ДобавитьСтроку(Текст);
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Событие);
+КонецПроцедуры
+
+&ТочкаМаршрута("sendbyid/{ид}")
+Процедура ОтправитьПоИд(ид) Экспорт
+ Событие = ФабрикаОтветов.СерверноеСобытие();
+ Событие.ДобавитьСтроку("привет");
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеПоИдСоединения(ид, Событие);
+КонецПроцедуры
+
+&ТочкаМаршрута("sendbut/{ид}")
+Процедура ОтправитьКроме(ид) Экспорт
+ Событие = ФабрикаОтветов.СерверноеСобытие();
+ Событие.ДобавитьСтроку("привет");
+
+ Массив = Новый Массив;
+ Массив.Добавить(ид);
+
+ БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсемКроме(ИмяТопика, Событие, Массив);
+КонецПроцедуры
\ No newline at end of file
diff --git "a/tests/app/\320\241\320\241\320\225\320\232\320\273\320\270\320\265\320\275\321\202.os" "b/tests/app/\320\241\320\241\320\225\320\232\320\273\320\270\320\265\320\275\321\202.os"
new file mode 100644
index 0000000..7c8dab9
--- /dev/null
+++ "b/tests/app/\320\241\320\241\320\225\320\232\320\273\320\270\320\265\320\275\321\202.os"
@@ -0,0 +1,74 @@
+#Использовать logos
+
+Перем Сообщения;
+Перем Соединение;
+Перем ПервичныйОтвет;
+Перем Слушать;
+Перем Лог;
+
+Процедура ПриСозданииОбъекта(Хост, Порт, Топик)
+ Лог = Логирование.ПолучитьЛог("oscript.app.winow.test.sseclient");
+ Сообщения = Новый Массив();
+ Соединение = Новый TCPСоединение(Хост, Порт);
+ УстановитьПодключение(Топик);
+ Слушать = Истина;
+ НачатьПрослушку();
+КонецПроцедуры
+
+Процедура Остановить() Экспорт
+ Слушать = Ложь;
+КонецПроцедуры
+
+Процедура ЖдатьСообщения() Экспорт
+ Пока Слушать Цикл
+ Ответ = Соединение.ПрочитатьСтроку();
+ Если НЕ ПустаяСтрока(Ответ) Тогда
+ Если СтрНайти(Ответ, "ping") = 0 Тогда
+ Сообщения.Добавить(СокрЛП(Ответ));
+ Лог.Отладка("Принято сообщение: " + Ответ);
+ КонецЕсли;
+ КонецЕсли;
+ Приостановить(100);
+ КонецЦикла;
+ Соединение.Закрыть();
+ Соединение = Неопределено;
+ Лог.Отладка("Соединение закрыто");
+КонецПроцедуры
+
+Процедура НачатьПрослушку()
+ ФоновыеЗадания.Выполнить(ЭтотОбъект, "ЖдатьСообщения");
+КонецПроцедуры
+
+Функция ПервичныйОтвет() Экспорт
+ Возврат ПервичныйОтвет;
+КонецФункции
+
+Функция Сообщения() Экспорт
+ Возврат Сообщения;
+КонецФункции
+
+Процедура УстановитьПодключение(Топик)
+ Лог.Отладка("Установка соединения");
+ ТекстПодключения = ШаблонПодключения();
+
+ Соединение.ОтправитьСтроку(СтрШаблон(ТекстПодключения, Топик));
+
+ ПервичныйОтвет = Соединение.ПрочитатьСтроку();
+
+ Если Не СтрНачинаетсяС(ПервичныйОтвет, "HTTP/1.1 200 OK") Тогда
+ ВызватьИсключение "Не удалось подключиться к топику " + Топик + " по причине: " + ПервичныйОтвет;
+ КонецЕсли;
+ Лог.Отладка("Соединение установлено");
+КонецПроцедуры
+
+Функция ШаблонПодключения()
+
+ Текст =
+ "GET %1 HTTP/1.1
+ |Accept: text/event-stream
+ |Connection: keep-alive
+ |";
+
+ Возврат Текст;
+
+КонецФункции