Skip to content

Commit

Permalink
Feature/sse (#70)
Browse files Browse the repository at this point in the history
Добавлена поддержка SSE
  • Loading branch information
Nivanchenko authored Jul 8, 2024
1 parent b6486b5 commit a056184
Show file tree
Hide file tree
Showing 22 changed files with 1,148 additions and 17 deletions.
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ opm install winow
- Работать с шаблонами ответов (Синтаксис шаблона чем-то похож на jinja2, но сильно упрощен).
- Базовая авторизация и управление доступом к страницам по ролям.
- Использовать протокол WebSocket
- Использовать протокол server-sent events. (SSE)

## Ограничения ?

Expand Down Expand Up @@ -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).
Expand Down
83 changes: 83 additions & 0 deletions example/hwapp/view/main_sse.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@

<!DOCTYPE html>

<script>
let eventSource;
let likeElem;
let watchElem;
let commentsElem;

function start() {
if (!window.EventSource) {
// Internet Explorer или устаревшие браузеры
alert("Ваш браузер не поддерживает EventSource.");
return;
}

likeElem = document.getElementById("likeCount");
watchElem = document.getElementById("watchCount");
commentsElem = document.getElementById("commentList");

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);
});

}


function addComment(msg) {
commentsElem.innerHTML += msg;
}

function likeClick(){
let xhr = new XMLHttpRequest();
xhr.open('GET', 'sse/addLike');
xhr.send();
}

function sendComment(){
let formData = new FormData(document.forms.comments);

let json = JSON.stringify({
name: formData.get("name"),
Comment: formData.get("comment")
});

let xhr = new XMLHttpRequest();
xhr.open("POST", "/sse/postcomment");
xhr.setRequestHeader('Content-Type', 'application/json; charset=utf-8');
xhr.send(json);

}

window.onload = start;

</script>

<img src="images/zl1.jpg" width="150" height="150" alt="Вино и желуди">
<div>
<span id="likeCount">0</span> <a href="#" style="text-decoration: none;" onclick="likeClick();return false;" title="Like"><span>&#x2764;</span></a>
</div>
<div>Сейчас смотрят: <span id="watchCount">0</span></div>
<form name="comments">
<label for="name">Имя:</label>
<input id="name" name="name" value="">
<br>
<label for="comment">Комментарий:</label>
<div>
<textarea name="comment" id="comment" style="font-family:sans-serif;font-size:1.2em;">
</textarea>
<button type="button" onclick="sendComment()">Отправить</button>
</div>
</form>
<div id="commentList"></div>
110 changes: 110 additions & 0 deletions example/hwapp/СерверныеСобытия.os
Original file line number Diff line number Diff line change
@@ -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")
.ДобавитьСтроку(Строка(КоличествоЛайков));
БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);
Исключение
Сообщить(ОписаниеОшибки());
КонецПопытки;
КонецПроцедуры

Процедура ОповеститьОКомментарии(СтрокаКомментария)

Сообщение = СообщениеИзСтрокиКомментария(СтрокаКомментария);

БрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);

КонецПроцедуры

Функция СообщениеИзСтрокиКомментария(СтрокаКомментария)
СтрокаШаблон = "<div class='comment'><div class='comment-header'><span class='comment-name'>%1</span> - <span class='comment-date'>%2</span></div>
|<div class='comment-body'>%3</div></div>";
Текст = СтрШаблон(СтрокаШаблон, СтрокаКомментария.Имя, СтрокаКомментария.Дата, СтрокаКомментария.Комментарий);

Сообщение = ФабрикаОтветов.СерверноеСобытие();
Сообщение.ТипСобытия("newComment")
.ДобавитьСтроку(Текст);

Возврат Сообщение;
КонецФункции

Процедура ДобавитьКомментарий(Имя, Комментарий)

НовыйКомментарий = Комментарии.Добавить();
НовыйКомментарий.Имя = Имя;
НовыйКомментарий.Комментарий = Комментарий;
НовыйКомментарий.Дата = ТекущаяДата();

ОповеститьОКомментарии(НовыйКомментарий);

КонецПроцедуры
5 changes: 3 additions & 2 deletions packagedef
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@


Описание.Имя("winow")
.Версия("0.7.0")
.Версия("0.8.0")
.Автор("Никита Иванченко")
.АдресАвтора("https://github.com/Nivanchenko")
.Описание("Минималистичный веб-сервер на нативном OneScript")
Expand All @@ -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")
Expand Down
Loading

0 comments on commit a056184

Please sign in to comment.