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 +
+
Сейчас смотрят: 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") + .ДобавитьСтроку(Строка(КоличествоЛайков)); + БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение); + Исключение + Сообщить(ОписаниеОшибки()); + КонецПопытки; +КонецПроцедуры + +Процедура ОповеститьОКомментарии(СтрокаКомментария) + + Сообщение = СообщениеИзСтрокиКомментария(СтрокаКомментария); + + БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение); + +КонецПроцедуры + +Функция СообщениеИзСтрокиКомментария(СтрокаКомментария) + СтрокаШаблон = "
%1 - %2
+ |
%3
"; + Текст = СтрШаблон(СтрокаШаблон, СтрокаКомментария.Имя, СтрокаКомментария.Дата, СтрокаКомментария.Комментарий); + + Сообщение = ФабрикаОтветов.СерверноеСобытие(); + Сообщение.ТипСобытия("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 + |"; + + Возврат Текст; + +КонецФункции