diff --git a/.vitepress/config.mts b/.vitepress/config.mts
index eae9f72..7eefe0f 100644
--- a/.vitepress/config.mts
+++ b/.vitepress/config.mts
@@ -18,7 +18,8 @@ export default defineConfig({
'script',
{},
"window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-ZHG1MYKGV9');"
- ]
+ ],
+ ['link', { rel: 'icon', href: '/docs/favicon.ico' }]
],
appearance: 'dark',
@@ -51,6 +52,10 @@ export default defineConfig({
},
+ ignoreDeadLinks: [
+ /^https?:\/\/localhost/
+ ],
+
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
@@ -67,7 +72,8 @@ export default defineConfig({
contentRoot: 'docs/',
contentDirs: [
{ text: 'Начало работы', dir: 'getting-started' },
- { text: 'Использование фреймворка', dir: 'framework-elements' }
+ { text: 'Использование фреймворка', dir: 'framework-elements' },
+ { text: 'Winow', dir: 'winow' }
],
collapsed: false,
}),
diff --git a/docs/public/acorn-ol.png b/docs/public/acorn-ol.png
deleted file mode 100644
index 8415b66..0000000
Binary files a/docs/public/acorn-ol.png and /dev/null differ
diff --git a/docs/static/acorn.png b/docs/static/acorn.png
deleted file mode 100644
index e55e4a7..0000000
Binary files a/docs/static/acorn.png and /dev/null differ
diff --git a/docs/static/telegram-white-icon.svg b/docs/static/telegram-white-icon.svg
deleted file mode 100644
index 471c7fa..0000000
--- a/docs/static/telegram-white-icon.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/static/winow/hw1.png b/docs/static/winow/hw1.png
new file mode 100644
index 0000000..1272abb
Binary files /dev/null and b/docs/static/winow/hw1.png differ
diff --git a/docs/static/winow/hw10.png b/docs/static/winow/hw10.png
new file mode 100644
index 0000000..093b7e1
Binary files /dev/null and b/docs/static/winow/hw10.png differ
diff --git a/docs/static/winow/hw11.png b/docs/static/winow/hw11.png
new file mode 100644
index 0000000..6fd9505
Binary files /dev/null and b/docs/static/winow/hw11.png differ
diff --git a/docs/static/winow/hw12.png b/docs/static/winow/hw12.png
new file mode 100644
index 0000000..42164ca
Binary files /dev/null and b/docs/static/winow/hw12.png differ
diff --git a/docs/static/winow/hw2.png b/docs/static/winow/hw2.png
new file mode 100644
index 0000000..9438e32
Binary files /dev/null and b/docs/static/winow/hw2.png differ
diff --git a/docs/static/winow/hw3.png b/docs/static/winow/hw3.png
new file mode 100644
index 0000000..60959af
Binary files /dev/null and b/docs/static/winow/hw3.png differ
diff --git a/docs/static/winow/hw4.png b/docs/static/winow/hw4.png
new file mode 100644
index 0000000..0a13686
Binary files /dev/null and b/docs/static/winow/hw4.png differ
diff --git a/docs/static/winow/hw5.png b/docs/static/winow/hw5.png
new file mode 100644
index 0000000..217ebcd
Binary files /dev/null and b/docs/static/winow/hw5.png differ
diff --git a/docs/static/winow/hw6.png b/docs/static/winow/hw6.png
new file mode 100644
index 0000000..30de072
Binary files /dev/null and b/docs/static/winow/hw6.png differ
diff --git a/docs/static/winow/hw7.png b/docs/static/winow/hw7.png
new file mode 100644
index 0000000..f4ba94a
Binary files /dev/null and b/docs/static/winow/hw7.png differ
diff --git a/docs/static/winow/hw8.png b/docs/static/winow/hw8.png
new file mode 100644
index 0000000..65bacba
Binary files /dev/null and b/docs/static/winow/hw8.png differ
diff --git a/docs/static/winow/hw9.png b/docs/static/winow/hw9.png
new file mode 100644
index 0000000..01ccc16
Binary files /dev/null and b/docs/static/winow/hw9.png differ
diff --git a/docs/static/winow/sse.gif b/docs/static/winow/sse.gif
new file mode 100644
index 0000000..dfe7c20
Binary files /dev/null and b/docs/static/winow/sse.gif differ
diff --git a/docs/static/winow/ws-chat.gif b/docs/static/winow/ws-chat.gif
new file mode 100644
index 0000000..2e0e95b
Binary files /dev/null and b/docs/static/winow/ws-chat.gif differ
diff --git a/docs/winow/index.md b/docs/winow/index.md
new file mode 100644
index 0000000..ec1f11c
--- /dev/null
+++ b/docs/winow/index.md
@@ -0,0 +1,1299 @@
+# WINOW is not OneScript.web
+
+Минималистичный веб-сервер, построен на нативном TCPСервер, и работает на желудях.
+
+Зачем это нужно, когда есть OneScript.Web, -CGI и т.д.? Отвечаю - для того, чтобы все было на чистом OneScript! И потому, что могу. С полным контролем, от входа двоичных данных на порт, до определения маршрута, получения данных, генерации ответа по шаблону и отправкой обратно клиенту.
+
+## Установка
+
+```
+opm install winow
+```
+
+## Книга жалоб и пожеланий !
+
+Можно оставить тут https://github.com/autumn-library/winow/issues или тут https://github.com/oscript-library/winow/issues
+
+## Что можно сделать ?
+
+Данная библиотека позволит Вам достаточно просто подготовить и запустить:
+
+- микросервис, с гибким API
+- быстро сделать МОК для вашего "любимого" удаленного API и наконец-то продолжить комфортную разработку.
+- Веб приложение, с отдачей статичных файлов, разграничением доступа по ролям, и генерацией страниц по шаблонам.
+- И все, на что хватит фантазии.
+
+## Какие возможности ?
+
+В данной библиотеке я постарался реализовать подход в разработке приложений в стиле MVC.
+
+На текущий момент winow позволяет:
+
+- Обрабатывать входящие GET и POST запросы.
+- Обеспечивать маршрутизацию входящего запроса до нужного метода.
+- Разбирать все входящие параметры.
+- Обрабатывать тело входящего POST запроса.
+- Работать с печеньками (Cookie).
+- Работать с сессиями.
+- Отдавать статичные файлы (картинки, архивы и т.д.)
+- Работать с шаблонами ответов (Синтаксис шаблона чем-то похож на jinja2, но сильно упрощен).
+- Базовая авторизация и управление доступом к страницам по ролям.
+- Использовать протокол WebSocket
+- Использовать протокол server-sent events. (SSE)
+
+## Ограничения ?
+
+Да! Нет никаких обещаний на тему больших нагрузок. И нет поддержки https, погружаться в историю с шифрованием трафика, я еще не готов.
+
+## Как, из чего, зависимости ?
+
+Библиотека разработана с использованием фреймворка для инверсии зависимостей - https://github.com/autumn-library/autumn. Для более эффективной работы с winow, следует ознакомиться. А так же обязательно пройти по ссылке и поставить звездочку, без этого ничего работать не будет.
+
+## Хеллоу ворлд !
+
+От слов - к делу. Чтобы понять, как это все работает, давайте сделаем hello-world приложение, которое будет запускаться на localhost:3333 и отвечать простым текстом hello-world.
+
+Первым делом, нам нужна точка входа, которая запустит приложение.
+
+Создадим такой файл:
+```
+ПриветМир.os
+```
+
+```bsl
+#Использовать autumn
+#Использовать winow
+
+Поделка = Новый Поделка;
+Поделка.ЗапуститьПриложение();
+```
+
+Теперь можете запустить файл ```ПриветМир.os``` и ничего не будет работать. И причин для этого ровно две. Первая - вы не сходили https://github.com/autumn-library/autumn и не поставили звезду. Вторая - мы не создали каталог, с классами, которые обрабатывают логику запросов. Я верю, что вы успели сходить и поставить звезду! Перейдем к созданию логики.
+
+Создаем каталог и файл:
+
+```
+app/КонтролПриветствия.os
+```
+
+```bsl
+&Контроллер("/")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+&ТочкаМаршрута("/")
+Процедура Приветствие(Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ Ответ.ТелоТекст = СтрШаблон("
+ |
%1
", "Привет новый дивный мир !");
+
+КонецПроцедуры
+```
+
+И снова пробуем запустить ```ПриветМир.os```, и идем в http://localhost:3333/
+
+И чудо свершилось:
+
+![hw1](../static/winow/hw1.png)
+
+## Передача параметров в строке запроса.
+
+После продолжительного восторга, двигаемся дальше. На новом примере разберем по частям, как это работает.
+
+Давайте сделаем еще один контроллер, еще более интерактивный. Сделаем так, что приложение будет нас встречать по имени. Имя мы хотим передавать в параметрах строки запроса.
+
+http://localhost:3333/greeter/getparams?name=Nikita&familia=ivanchenko
+
+Где ```greeter``` путь до нашего контроллера. И ```getparams``` точка входа для метода, который обрабатывает запрос. Все что после ```?``` именные параметры.
+
+Поехали, создаем файл:
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+```bsl
+&Контроллер("/greeter")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+&ТочкаМаршрута("getparams")
+Процедура Приветствие(Запрос, Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ Имя = Запрос.ПараметрыИменные["name"];
+ Фамилия = Запрос.ПараметрыИменные["familia"];
+
+ Ответ.ТелоТекст = СтрШаблон("
+ |
Имя: %1
+ |
Фамилия: %2
", Имя, Фамилия);
+
+КонецПроцедуры
+```
+
+Опять запускаем ```ПриветМир.os```, и идем теперь вот так http://localhost:3333/greeter/getparams?name=Никита&familia=Иванченко
+
+![hw2](../static/winow/hw2.png)
+
+Снова полный успех! Но давайте подробней остановимся на каждом этапе этого чуда.
+
+Допустимо передавать именные параметры по имени в метод точки маршрута
+
+```bsl
+&ТочкаМаршрута("getparamsbyname")
+Процедура ПроверкаГетПараметровПоИмени(Ответ, ИмяКошки, ИмяСобаки) Экспорт
+ Ответ.УстановитьТипКонтента("txt");
+ Ответ.ТелоТекст = СтрШаблон("%1 И %2", "Кошка=" + ИмяКошки, "Собака=" + ИмяСобаки);
+КонецПроцедуры
+```
+
+Файл, который мы только что сделали, описывает определенную точку в адресной строке. При совпадении с которой перехватывается управление над входящим запросом. Посмотрим поближе.
+
+```bsl
+&Контроллер("/greeter")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+```
+
+В начале идет конструктор нашего класса, ```ПриСозданииОбъекта()```. Весь код, который в нем написан, будет выполнен при создании. Удобно тут выполнять всякую инициализацию переменных.
+
+Этот метод имеет аннотацию ```&Контроллер("/greeter")``` как раз указывает, путь от корня, после которого будет осуществлен перехват.
+
+Стоит отметить что аннотация может быть более длинной, чтобы отвечать логике описания api. Например, ```&Контроллер("/app/api/v1/greeter")``` тоже рабочий вариант, только ходить нужно уже вот сюда http://localhost:3333/app/api/v1/greeter
+
+У любого контроллера может быть любое множество методов, которыми он обрабатывает входящий запрос.
+
+```bsl
+&ТочкаМаршрута("getparams")
+Процедура Приветствие(Запрос, Ответ) Экспорт
+```
+
+Для того, чтобы процедура контроллера могла понимать, что ее вызывают из запроса, ее нужно пометить аннотацией ```&ТочкаМаршрута("getparams")```. Где параметр аннотации указывает имя в пути, после которого ей нужно сработать.
+
+Так же, чтобы все получилось, процедура должна отвечать нескольким требованиям:
+
+- Быть экспортной
+- Принимать на вход параметры, имена которых ограничены и предопределены. Назначение параметров мы разберем по ходу дела.
+
+```Запрос```, например, хранит всю информацию, которая пришла к нам от клиента. В том числе ```Запрос.ПараметрыИменные``` - соответствие, хранящее значения всех параметров, которые переданы после знака ```?```
+
+Дальше мы лихо эти параметры читаем.
+
+```bsl
+Имя = Запрос.ПараметрыИменные["name"];
+Фамилия = Запрос.ПараметрыИменные["familia"];
+```
+
+Следующий параметр ```Ответ```, в котором собирается все, что будет отправлено обратно клиенту. Например, вот так:
+
+```bsl
+Ответ.УстановитьТипКонтента("html");
+```
+
+Устанавливается заголовок ```Content-Type```, благодаря которому браузер понимает, как отобразить то, что мы ему шлем.
+
+По умолчанию поддерживаются типы:
+
+```bsl
+ОписанияТиповРасширений = Новый Соответствие();
+ОписанияТиповРасширений.Вставить("htm","text/html; charset=utf-8");
+ОписанияТиповРасширений.Вставить("html","text/html; charset=utf-8");
+ОписанияТиповРасширений.Вставить("css","text/css");
+ОписанияТиповРасширений.Вставить("js","text/javascript");
+ОписанияТиповРасширений.Вставить("jpg","image/jpeg");
+ОписанияТиповРасширений.Вставить("jpeg","image/jpeg");
+ОписанияТиповРасширений.Вставить("png","image/png");
+ОписанияТиповРасширений.Вставить("gif","image/gif");
+ОписанияТиповРасширений.Вставить("ico","image/x-icon");
+ОписанияТиповРасширений.Вставить("zip","application/x-compressed");
+ОписанияТиповРасширений.Вставить("rar","application/x-compressed");
+ОписанияТиповРасширений.Вставить("json","application/json");
+ОписанияТиповРасширений.Вставить("txt","text/plain; charset=utf-8");
+```
+
+Ну и конечно же устанавливаем текст ответа, который вернется клиенту.
+
+```bsl
+Ответ.ТелоТекст = СтрШаблон("
+ |
Имя: %1
+ |
Фамилия: %2
", Имя, Фамилия);
+```
+
+Сейчас это не удобно и не красиво. Но к концу нашей беседы мы разберемся - как сделать красиво.
+
+## Описание возможных параметров ТочкиМаршрута
+
+* ```Запрос``` - Объект, содержащий все данные о входящем запросе
+
+* ```Ответ``` - Объект, содержащий все данные об ответе, который будет отправлен пользователю
+
+* ```Сессия``` - Объект, хранящий сессионные данные пользователя. Имеет поля ```Данные```, соответствие для хранения любых данных бизнес-логики и ```Логин```, строковое имя пользователя, после авторизации.
+
+Так же, для удобства, можно получать части объектов ```Сессия``` и ```Запрос```.
+
+* ```Логин``` - Логин, хранящейся в сессии. Аналог ```Сессия.Логин```.
+
+* ```ДанныеСессии``` - Соответствие с данными, хранящееся в сессии. Аналог ```Сессия.Данные```.
+
+* ```ТекстЗапроса``` - Полный текст входящего запроса.
+
+* ```ЗаголовкиЗапроса``` - Соответствие заголовков запроса.
+
+* ```ТелоЗапроса``` - Текст тела запроса.
+
+* ```ТелоЗапросаДвоичныеДанные``` - Тело запроса в виде двоичных данных.
+
+* ```МетодЗапроса``` - Метод (GET, POST, PUT и тд).
+
+* ```ПолныйПутьЗапроса``` - Полный путь запроса.
+
+* ```ПутьЗапроса``` - Путь без параметров.
+
+* ```ПараметрыЗапросаИменные``` - Соответствие, с именными параметрами.
+
+* ```ПараметрыЗапросаПорядковые``` - Массив с запросами.
+
+* ```ДатаПолученияЗапроса``` - Время получения запроса.
+
+* ```ДвоичныеДанныеЗапроса``` - Двоичные данные всего запроса.
+
+* ```КукиЗапроса``` - Куки.
+
+## Еще один способ передачи параметров в строке запроса.
+
+Предыдущий пример показал, как можно передать параметры в строке запроса, при этом параметры имели имена. Теперь рассмотрим пример, когда параметры упорядоченные.
+
+Давайте сделаем наконец калькулятор! И будет он работать вот так:
+
+```
+http://localhost:3333/greeter/calc////
+```
+Где ```calc``` - точка маршрута. ```operation``` - вид операции, будем поддерживать minus и plus. и следом два слагаемых нашего уравнения.
+
+Добавим в наш
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+новую точку маршрута:
+
+```bsl
+&ТочкаМаршрута("calc")
+Процедура Калькулятор(ПараметрыЗапросаПорядковые, Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ Если ПараметрыЗапросаПорядковые.Количество() <> 3 Тогда
+
+ Решение = "Неверное число параметров";
+
+ ИначеЕсли Не (ПараметрыЗапросаПорядковые[0] = "minus"
+ И Не ПараметрыЗапросаПорядковые[0] = "plus") Тогда
+ Решение = "Операция не распознана";
+
+ Иначе
+ Попытка
+ Число1 = Число(ПараметрыЗапросаПорядковые[1]);
+ Число2 = Число(ПараметрыЗапросаПорядковые[2]);
+
+ Если ПараметрыЗапросаПорядковые[0] = "minus" Тогда
+ Решение = Число1 - Число2;
+ Иначе
+ Решение = Число1 + Число2;
+ КонецЕсли
+
+ Исключение
+ Решение = "Ошибка конвертации в число"
+ КонецПопытки;
+ КонецЕсли;
+
+ Ответ.ТелоТекст = СтрШаблон("
+ |
Ответ: %1
", Решение);
+
+КонецПроцедуры
+```
+
+Перезапустим приложение, и перейдем по ссылке http://localhost:3333/greeter/calc/plus/3/2
+
+И в ответ перед нами будет красоваться
+
+![hw3](../static/winow/hw3.png)
+
+На самом деле, тут все очень просто. Когда мы объявляем точку маршрута ```&ТочкаМаршрута("calc")```, все что дальше в пути через ```/``` будет любезно складываться в массив ```ПараметрыЗапросаПорядковые```. А что делать с массивами, вы и без меня знаете.
+
+## Шаблоны параметров в строке запроса
+
+Так же есть возможность задавать шаблон строки маршрута, где можно задавать именные параметры.
+
+Например:
+
+```bsl
+&ТочкаМаршрута("calc/{Число1}/multiply/{Число2}")
+Процедура ШаблонныеПараметрыУмножение(Ответ, Число1, Число2) Экспорт
+ Ответ.УстановитьТипКонтента("txt");
+ Ответ.ТелоТекст = Число(Число1) * Число(Число2);
+КонецПроцедуры
+```
+
+В точке маршрута фигурными скобками указываем параметры ```Число1``` и ```Число2```, и эти параметры будут переданы в метод обработчик во время выполнения запроса.
+
+## Входящие POST запросы
+
+С пост запросами, все почти так же просто. ```Запрос``` Имеет два поля ```Тело``` и ```ТелоДвоичныеДанные```, т.к. пользователь может закинуть нам как текст, так и картинку например.
+
+Давайте потренируемся в обработке таких запросов, усовершенствуем наше приложение и научим его возводить в степень переданное число.
+
+Вводить число мы будем по адресу http://localhost:3333/greeter/inputstepen, где будет форма ввода числа, и кнопка расчета. После расчета мы будем перенаправлены на http://localhost:3333/greeter/resultstepen. Форма будет передавать параметры методом POST.
+
+Для реализации этой задумки добавим в наш
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+этот код, с двумя новыми точками маршрута
+
+```bsl
+&ТочкаМаршрута("inputstepen")
+Процедура ВводСтепени(Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ Ответ.ТелоТекст =
+ " ";
+
+КонецПроцедуры
+
+&ТочкаМаршрута("resultstepen")
+Процедура ВозводительВСтепень(Запрос, Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ ПостПараметры = Парсеры.ПараметрыИзТекста(Запрос.Тело);
+
+ Попытка
+ Решение = Pow(ПостПараметры["chislo"], ПостПараметры["stepen"]);
+ Исключение
+ Решение = "Ошибка при расчетах " + ОписаниеОшибки();
+ КонецПопытки;
+
+ Ответ.ТелоТекст = СтрШаблон("
+ |
Ответ: %1
", Решение);
+
+КонецПроцедуры
+```
+
+![hw4](../static/winow/hw4.png)
+
+![hw5](../static/winow/hw5.png)
+
+Теперь разберемся, что тут произошло. Не буду останавливаться на описании HTML тегов, для этого в интернете сайтов больше, чем звезд на небе.
+
+Точка маршрута ```inputstepen``` показала нам форму, которая при расчете перенаправляет нас на ```resultstepen``` и в теле запроса передает параметры формы, которые имеют вид ```chislo=2&stepen=3```. Все что нам осталось, это обработать запрос.
+
+Мы можем парсить самостоятельно, но можно внедрить объект ```Парсеры```, который умеет парсить параметры в таком формате ```Парсеры.ПараметрыИзТекста(<СтрокаСПараметрами>)```. Этот метод вернет соответствие со значениями, которые в последствии нужно правильно использовать.
+
+Если во входящем запросе придет заголовок ```"Content-Type:application/json"``` тогда его тело автоматом будет распарсено в структуру ```ТелоЗапросОбъект``` с которой можно работать.
+
+```bsl
+&ТочкаМаршрута("postjsonbody")
+Процедура ПроверкаПостЗапросаКакОбъект(Ответ, ТелоЗапросОбъект, ЗаголовкиЗапроса) Экспорт
+ Ответ.УстановитьТипКонтента("txt");
+ Ответ.ТелоТекст = СтрШаблон("%1 %2", ТелоЗапросОбъект.Имя, ТелоЗапросОбъект.Фамилия);
+КонецПроцедуры
+```
+
+Если во входящем запросе придет заголовок ```"Content-Type:application/x-www-form-urlencoded"``` тогда его тело автоматом будет распарсено по именам и значениям, которые будет принимать метод точки маршрута
+
+```bsl
+&ТочкаМаршрута("postformbody")
+Процедура ПроверкаПостЗапросаКакФорма(Ответ, Имя, Фамилия) Экспорт
+ Ответ.УстановитьТипКонтента("txt");
+ Ответ.ТелоТекст = СтрШаблон("%1 %2", Имя, Фамилия);
+КонецПроцедуры
+```
+
+## Работа с куками
+
+Куки, это возможность сохранить на клиенте, в браузере, какую-либо информацию.
+
+Объекты ```Запрос``` и ```Ответ```, которые мы получаем в метод, который мы помечаем как ```ТочкаМаршрута```. Оба этих объекта имеют свойство ```Куки```. Соответственно во входящем запросе их можно читать, а в ответе устанавливать.
+
+Модернизируем файл
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+```bsl
+&ТочкаМаршрута("setcookie")
+Процедура УстановитьКуку(Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ ИмяКуки = "ДатаПоследнегоВхода";
+ ЗначениеКуки = ТекущаяДата();
+
+ НоваяКука = Ответ.Куки.Добавить(ИмяКуки, ЗначениеКуки);
+
+ Ответ.ТелоТекст = "
+ |
", ЗначениеКуки);
+
+КонецПроцедуры
+```
+
+![hw6](../static/winow/hw6.png)
+
+![hw7](../static/winow/hw7.png)
+
+Еще раз, не забываем, что куки хранятся на стороне браузера.
+
+## Хранение данных сессии
+
+Еще один параметр точки маршрута ```Сессия``` имеет поле ```Данные```. По сути, это соответствие, в которое можно записывать и читать любые значения. Эти данные хранятся на сервере, пока он работает. При остановке, данные сессии пропадают. При необходимости, в рамках приложения, можно дописать хранение данных сессии в файлах, базах данных и т.д.
+
+Еще один пример
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+```bsl
+&ТочкаМаршрута("setsessiondata")
+Процедура УстановитьДанныеСессии(Ответ, Сессия) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ ИмяПараметраСессии = "ДатаПоследнегоВхода";
+ ЗначениеПараметраСессии = ТекущаяДата();
+
+
+ Сессия.Данные[ИмяПараметраСессии] = ЗначениеПараметраСессии;
+
+ Ответ.ТелоТекст = "
+ |
", ЗначениеПараметраСессии);
+
+КонецПроцедуры
+```
+
+![hw8](../static/winow/hw8.png)
+
+![hw9](../static/winow/hw9.png)
+
+## Публикация статичных файлов.
+
+Часто нужно открывать доступ для скачивания всевозможных файлов. Таких как картинки, js-скрипты, css и т.д.
+
+Для этого нужно сконфигурировать сервер, указав в файле ```autumn-properties.json``` нужные параметры. Этот файл нужно положить рядом с ```ПриветМир.os```
+
+```
+autumn-properties.json
+```
+```JSON
+{ "winow":
+ {
+ "КаталогиСФайлами": {
+ "/images": "./app/files"
+ }
+ }
+}
+```
+
+Про конфигурирование через этот файл расскажу ниже, а пока давайте попробуем сделать наше приложение повеселее и добавить картинок.
+
+Добавим в каталог приложения пару картинок.
+
+```
+app/files/zl1.jpg
+app/files/fun/zl2.jpg
+```
+
+![hw10](../static/winow/hw10.png)
+
+Как видим, по заданному пути теперь доступны файлы из каталога, при чем с сохранением внутренней иерархии каталога файлов.
+
+Стоит отметить, что доступны становятся не все файлы сразу, а только те, расширения которых описаны в соответствии
+
+```bsl
+ОписанияТиповРасширений = Новый Соответствие();
+ОписанияТиповРасширений.Вставить("htm","text/html; charset=utf-8");
+ОписанияТиповРасширений.Вставить("html","text/html; charset=utf-8");
+ОписанияТиповРасширений.Вставить("css","text/css");
+ОписанияТиповРасширений.Вставить("js","text/javascript");
+ОписанияТиповРасширений.Вставить("jpg","image/jpeg");
+ОписанияТиповРасширений.Вставить("jpeg","image/jpeg");
+ОписанияТиповРасширений.Вставить("png","image/png");
+ОписанияТиповРасширений.Вставить("gif","image/gif");
+ОписанияТиповРасширений.Вставить("ico","image/x-icon");
+ОписанияТиповРасширений.Вставить("zip","application/x-compressed");
+ОписанияТиповРасширений.Вставить("rar","application/x-compressed");
+ОписанияТиповРасширений.Вставить("json","application/json");
+ОписанияТиповРасширений.Вставить("txt","text/plain; charset=utf-8");
+```
+
+При желании этот список можно расширить.
+
+## Работа с шаблонами страниц.
+
+Если вы дочитали до этого пункта, я в первую очередь Вам благодарен. И в знак уважения, расскажу про механизм шаблонов. Я ведь раньше гордо заявил, что тут возможен подход MVC, так вот вы, наверное, все время задавались вопросом, где же V? И правда, писать код так:
+
+```bsl
+Ответ.ТелоТекст = СтрШаблон("
+ |
Имя: %1
+ |
Фамилия: %2
", Имя, Фамилия);
+```
+
+просто не удобно, и мало приличных слов для такого подхода можно подобрать, и ни в одном не будет буквы V. Но у меня есть решение!
+
+Сразу покажу пример, а потом разберем по строчкам. Давайте отобразим страницу, на которой выведем текущее время, совершенно псевдослучайное число, динамически выведем случайное количество строк, и попробуем поиграться с условиями.
+
+Поехали!
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+```bsl
+&Контроллер("/demoviews")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+&Отображение("./app/view/view1.html")
+&ТочкаМаршрута("demo1")
+Процедура ДемонстрацияОтображения(Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ ГСЧ = Новый ГенераторСлучайныхЧисел();
+
+ СлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 10);
+
+ Массив = Новый Массив();
+
+ Для Сч = 1 по СлучайноеЧисло Цикл
+ Массив.Добавить(Строка(Новый УникальныйИдентификатор()));
+ КонецЦикла;
+
+ Модель = Новый Структура();
+ Модель.Вставить("СлучайноеЧисло", СлучайноеЧисло);
+ Модель.Вставить("МассивСтрок", Массив);
+
+ Ответ.Модель = Модель;
+
+КонецПроцедуры
+```
+
+Что тут нового? Во первых у точки маршрута появилась аннотация ```&Отображение("./app/view/view1.html")```. А во вторых - определяется структура и устанавливается в ```Ответ.Модель```. В этом весь секрет. После работы метода, на сцену выходит шаблонизатор, найдет указанный шаблон и разложит данные из модели, в соответствии с разметкой.
+
+```
+app/view/view1.html
+```
+
+
+
+```html
+
+
+
+ Демонстрация работы отображений
+
+
+
+
Привет! Это отображение из шаблона. Точное время {{ ТекущаяДата() }}
+
+
Вот твое случайное число {{ Модель.СлучайноеЧисло }}
+
+ {% Если Модель.СлучайноеЧисло > 5 Тогда %}
+
+
Случайное число БОЛЬШЕ пяти
+
+ {% Иначе %}
+
+
Случайное число НЕ больше пяти
+
+ {% КонецЕсли; %}
+
+
Давай выведу строки из массива:
+
+ {% Для Каждого СтрокаИзМассива из Модель.МассивСтрок Цикл %}
+
+
Значение строки: {{ СтрокаИзМассива }} и оно достаточно случайно
+
+ {% КонецЦикла; %}
+
+
Ранее я объявил переменные, теперь покажу их
+
ОднаПеременная = {{ ОднаПеременная }}
+
ВтораяПеременная = {{ ВтораяПеременная }}
+
+
+
+```
+
+
+
+Если присмотреться, то шаблон это просто HTML разметка, которую смешали с 1сным кодом. Вот это коктейль получился!
+
+Основные принципы разметки:
+::: v-pre
+Выражения - обозначаются тегами. ```{{ <Выражение> }}```. Тут может быть:
+
+- Любое выражение на 1С, которое возвращает значение ```{{ 1 + 3 }}```
+- Переменная ```{{ Модель.ЛюбоеЗначение }}```
+- Функция ```{{ Макс(1,5,9,7) }}```
+
+
+
+Операторы - обозначаются тегами. ```{% <КодНа1С> %}```.
+
+
+:::
+Это полноценный код на 1С. Можно объявлять переменные, взаимодействовать с ```Модель```, использовать управляющие блоки(Циклы, Условия)
+
+![hw11](../static/winow/hw11.png)
+
+## Обработчики шаблонов.
+
+Может быть так, что до или после рендера модели в шаблоне, нужно выполнить некие манипуляции с текстом шаблона. Для выполнения этой операции нужно зарегистрировать обработчики событий до рендера и после. Например:
+
+
+::: v-pre
+```html
+
+```
+:::
+
+
+В этом шаблоне, мы хотим заменить вставки, неким текстом. Для этого добавим поделку два желудя.
+
+```bsl
+&Желудь
+&Прозвище("ПередОбработкойОтображения")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+Процедура Преобразовать(ТекстШаблона) Экспорт
+ ТекстШаблона = СтрЗаменить(ТекстШаблона, "@ТекстЗаменыДоРендера@", "Шапка");
+КонецПроцедуры
+```
+
+```bsl
+&Желудь
+&Прозвище("ПослеОбработкиОтображения")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+Процедура Преобразовать(ТекстШаблона) Экспорт
+ ТекстШаблона = СтрЗаменить(ТекстШаблона, "@ТекстЗаменыПослеРендера@", "Подвал");
+КонецПроцедуры
+```
+
+Тут мы добавили желуди с ```Прозвище``` "ПередОбработкойОтображения" и "ПослеОбработкиОтображения". Таких желудей может быть несколько. Но у каждого такого желудя должна быть процедура с именем ``Преобразовать```, в которую будет передан текст шаблона.
+
+## Компоненты.
+
+
+::: v-pre
+Писать шаблоны круто, но что может быть еще круче? Писать меньше шаблонов, и переиспользовать уже имеющиеся. Представим, что вам в разных местах нужно отображать одну и туже информацию, (таблицы, элементы меню, и т.д.). для решения этой задачи, шаблон имеет секретную функцию ```{{ ВывестиПоШаблону(<Путь до шаблона>, <Модель для шаблона>) }}```
+:::
+
+
+Давайте покажу, как это работает
+
+```
+app/ИнтерактивныйКонтролПриветствия.os
+```
+
+```bsl
+&Отображение("./app/view/view1.html")
+&ТочкаМаршрута("demo1")
+Процедура ДемонстрацияОтображения(Ответ) Экспорт
+
+ Ответ.УстановитьТипКонтента("html");
+
+ ГСЧ = Новый ГенераторСлучайныхЧисел();
+
+ СлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 10);
+
+ Массив = Новый Массив();
+
+ Для Сч = 1 по СлучайноеЧисло Цикл
+ Массив.Добавить(Строка(Новый УникальныйИдентификатор()));
+ КонецЦикла;
+
+ Модель = Новый Структура();
+ Модель.Вставить("СлучайноеЧисло", СлучайноеЧисло);
+ Модель.Вставить("МассивСтрок", Массив);
+
+ // добавим в модель второй массив
+ МассивФруктов = Новый Массив();
+ МассивФруктов.Добавить("Яблоко");
+ МассивФруктов.Добавить("Апельсин");
+ МассивФруктов.Добавить("Банан");
+ МассивФруктов.Добавить("Желудь");
+
+ Модель.Вставить("ВторойМассив", МассивФруктов);
+
+ Ответ.Модель = Модель;
+
+КонецПроцедуры
+```
+
+Шаблоны:
+
+```
+app/view/view1.html
+```
+
+
+
+```html
+
+
+
+ Демонстрация работы отображений
+
+
+
+
+
Привет! Это отображение из шаблона. Точное время {{ ТекущаяДата() }}
+
+
+{% Для Каждого СтрокаИзМассива из Модель Цикл %}
+
+
Значение строки: {{ СтрокаИзМассива }}
+
+{% КонецЦикла; %}
+
+```
+
+
+
+![hw12](../static/winow/hw12.png)
+
+## Общее отображение контрола.
+
+Для удобства разработки веб приложения хочется разделить отображения, и добавить что-то общее для всех точек маршрута. Например, общая html разметка, с заголовками, меню, подвалом и тд. Для этих целей есть возможность с помощью аннотации в конструкторе контрола указать общий шаблон.
+
+```bsl
+&Контроллер("/demoviews")
+&Отображение(Шаблон = "./hwapp/view/main.html", Метод = "ПолучитьМодельКонтрола")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+Функция ПолучитьМодельКонтрола(Запрос) Экспорт
+ Модель = Новый Структура("Заголовок, Дата", "Демонстрация работы отображений", Запрос.ДатаПолучения);
+
+ Возврат Модель;
+КонецФункции
+```
+
+Где ```&Отображение(Шаблон = "./hwapp/view/main.html", Метод = "ПолучитьМодельКонтрола")``` аннотация, указывает где расположен шаблон, и каким методом для него формируется модель с данными. Параметры этого метода так же могут быть выбраны, аналогично методам точек маршрута.
+
+А вот так выглядит общий шаблон
+
+
+
+```html
+
+
+
+{{Модель.Заголовок}}
+
+
+
Шапка страницы! Дата получения запроса: {{Модель.Дата}}
+
+@Контент
+
+
Подвал страницы
+
+
+```
+
+
+
+Где тег ```@Контент``` будет заменен результатом ответа точки маршрута.
+
+Однако бывают ситуации, когда у контроллера есть отображение, но какая точка маршрута должна возвращать ответ, без его применения. В такой ситуации, для метода точки маршрута нужно добавить аннотацию ```&НеВыводитОтображениеКонтроллера```. Например, вот так:
+
+```bsl
+&Отображение("./app/view/view1.html")
+&ТочкаМаршрута("demo2")
+&НеВыводитОтображениеКонтроллера
+Процедура ДемонстрацияОтображенияБезОбщегоОтображения(Ответ) Экспорт
+ ...
+КонецПроцедуры
+```
+
+
+## Ответы по ошибкам.
+
+Обрабатывая входящие запросы, могут случиться исключения. Ну кто с первого раза напишет правильно код? Сервер при исключении вернет страницу с кодом 500. Шаблон этой страницы можно переопределить. Моделью там будет структура
+
+```bsl
+Ответ.Модель = Новый Структура();
+Ответ.Модель.Вставить("КодСостояния", 500);
+Ответ.Модель.Вставить("ТекстСообщения", ТекстОшибки);
+Ответ.Модель.Вставить("Запрос", Запрос);
+```
+
+Ну а какой пользователь с первого раза введет без ошибки адрес ресурса? Сервер вернет ему 404. Шаблон этой ошибки так же можно переопределить. Модель там следующая
+
+```bsl
+Ответ.Модель = Новый Структура();
+Ответ.Модель.Вставить("КодСостояния", 404);
+Ответ.Модель.Вставить("ТекстСообщения", "Страница не найдена");
+Ответ.Модель.Вставить("Запрос", Запрос);
+```
+
+А вот таким не замысловатым способом можно переопределить шаблон стандартной ошибки
+
+```
+app/КонтролПриветствия.os
+```
+
+
+
+```bsl
+&ФинальныйШтрих
+Процедура ПостИнициализация() Экспорт
+ ОбщийКонтейнер.МенеджерОтображений.УстановитьШаблон404("
+ |
{{ Модель.КодСостояния }}
+ |
{{ Модель.ТекстСообщения }}
+ |
Искомый ресурс {{ Модель.Запрос.Путь }} не найден
");
+КонецПроцедуры
+```
+
+
+
+## Перенаправление
+
+Иногда бывает так, что нужно с одной страницы, перенаправить позльзователя на другую.
+
+Для этого, у объекта ```Ответ``` есть метод ```Перенаправить(<Адрес куда перенаправить>)```. Например, подобная точка маршрута будет перенаправлять запрос в корень приложения
+
+```bsl
+&ТочкаМаршрута("/redir")
+Процедура Перенаправление(Ответ) Экспорт
+ Ответ.Перенаправить("/");
+КонецПроцедуры
+```
+
+## Загрузка настроек из файла
+
+Настройки порта, имени хоста, каталогов файлов и приложений можно можно хранить в json файле, и загружать при старте приложения.
+
+Файл обязательно должен называться ```autumn-properties.json``` / ```autumn-properties.yml``` / ```autumn-properties.yaml```, и быть в корне запуска сервера или в подкаталоге ```src/```. Если какие-то значения не указаны, то они имеют значения по умолчанию.
+
+Пример:
+
+```JSON
+{ "winow":
+ {
+ "Порт": 3331, // по умолчанию 3333
+ "АвтоСтарт": true, // Управляет автоматическим запуском при Поделка.ЗапуститьПриложение() (по умолчанию Истина)
+ "ИмяХоста": "MySuperAPPHost", // по умолчанию localhost
+ "КаталогСПриложениями": "./hwapp", // по умолчанию "./app"
+ "РазмерБуфера": 1024, // Размер порции в байтах, которыми читаются данные из TCP соединения. (по умолчанию 1024)
+ "КаталогиСФайлами": {
+ "/images": "./hwapp/files" // значения по умолчанию нет
+ }
+ }
+}
+```
+
+## Управление доступом
+
+Для управления доступом к точке маршрута, предусмотрена аннотация ```&Роли("<Список ролей через запятую>")```. Все очень просто, и остается ответить только на один вопрос - как эти роли раздать, и как хранить данные входа пользователей. Пока это mvp, точного ответа не дам. Разработчик может самостоятельно придумать, как и где хранить группы и пароли. Я только покажу, как их подключить в наше приложение.
+
+```
+app/КонтролСУправлениемДоступом.os
+```
+
+```bsl
+&Пластилин
+Перем МенеджерДоступа Экспорт;
+
+&Контроллер("/sec")
+Процедура ПриСозданииОбъекта()
+
+КонецПроцедуры
+
+&ФинальныйШтрих
+Процедура ПроинициализироватьРоли() Экспорт
+
+ // Инициализация данных входа "пользователей"
+ МенеджерДоступа.ДобавитьТокен("Админ", "123");
+ МенеджерДоступа.ДобавитьТокен("Пользователь", "111");
+
+ // Назначение ролей "пользователям"
+ МенеджерДоступа.ДобавитьРольЛогина("Админ", "Администраторы");
+ МенеджерДоступа.ДобавитьРольЛогина("Админ", "Пользователи");
+
+ МенеджерДоступа.ДобавитьРольЛогина("Пользователь", "Пользователи");
+КонецПроцедуры
+
+// Точка доступная роли пользователи
+&Роли("Пользователи")
+&ТочкаМаршрута("user")
+Процедура Пользователь(Ответ) Экспорт
+ Ответ.ТелоТекст = "Пользователи";
+КонецПроцедуры
+
+// Точка доступная роли Администраторы
+&Роли("Администраторы")
+&ТочкаМаршрута("admin")
+Процедура Админ(Ответ) Экспорт
+ Ответ.ТелоТекст = "Админка";
+КонецПроцедуры
+
+// обычная точка, доступная всем
+&ТочкаМаршрута("free")
+Процедура Все(Ответ) Экспорт
+ Ответ.ТелоТекст = "Все подрят";
+КонецПроцедуры
+
+// Точка доступная ролям администраторы, пользователи.
+&Роли("Администраторы, Пользователи")
+&ТочкаМаршрута("usradm")
+Процедура АдминыИПользователи(Ответ) Экспорт
+ Ответ.ТелоТекст = "Админы и пользователи";
+КонецПроцедуры
+```
+
+## Работа с протоколом WebSocket
+
+В winow в экспериментальном виде реализована поддержка веб-сокетов. Спека не полная, поддерживаются пока только текстовые сообщения. Разберем работу протокола на примере онлайн чата. Я не буду углубляться в часть фронта. Вот [пример](https://github.com/autumn-library/winow/blob/master/example/hwapp/view/chat.html) реализации на фронте. Основная суть такая - когда мы заходим на контрол ```/chat``` осуществляется проверка, залогинился пользователь или нет. Если нет, переадресуем на страницу ввода логина.
+
+После ввода логина мы попадаем на страницу с чатом, где осуществляется подключение вебсокета, и начинается обмен сообщениями.
+
+А теперь разберем пример того, что происходит на стороне сервера. Обработкой входящих сообщений занимается такой же контроллер с точками маршрута, к которым мы привыкли. Каждая точка маршрута является "топиком" в рамках которого общается одно соединение веб сокета. В примере ниже у нас контроллер по адресу ```chat``` и точкой маршрута ```message```. У нас одно соединение, которое обменивается сообщениями в топике ```/chat/message```. Точка маршрута по обработке сообщений может принимать несколько параметров:
+- Идентификатор - идентификатор сессии, в которой можно хранить разные данные.
+- Топик - имя топика, в рамках которого происходит общение
+- Сообщение - расшифрованное сообщение, которое пришло от клиента.
+
+Для того, чтобы отправлять сообщения, нужно получить желудь ```БрокерСообщенийВебСокетов```. Который умеет следующие действия с сообщениями:
+
+- ОтправитьСообщение(Топик, Сообщение, Идентификатор) - Отправляет определенному клиенту сообщение в указанный топик.
+- ОтправитьСообщениеВсем(Топик, Сообщение) - Отправляет сообщение всем клиентам, подключенным к указанному топику.
+- ОтправитьСообщениеСписку(Топик, Сообщение, СписокИдентификаторов) - Отправляет сообщение массиву клиентов, подписанных на указанный топик.
+- ОтправитьСообщениеВсемКроме(Топик, Сообщение, СписокИсключенийИдентификаторов) - Отправляет сообщение всем, клиентам подписанным на указанный топик, кроме массива, переданного как параметр.
+
+Так же контроллер может иметь методы, помеченные аннотациями ```&ПриПодключенииВебСокета("/имя/топика")``` и ```ПриОтключенииВебСокета("/имя/топика")```. Которые будут вызваны, после соответствующих событий, и принимать ```Идентификатор``` клиента, с которым произошло событие.
+
+Вот полный пример с комментариями.
+
+```
+ВебСокетЧат.os
+```
+
+```bsl
+&Пластилин Перем БрокерСообщенийВебСокетов; // Инжектим желудь, который управляет отправкой сообщений
+
+Перем КешИменПользователей;
+
+&Контроллер("/chat") // помечаем контроллер и инициализируем кеш.
+Процедура ПриСозданииОбъекта()
+ КешИменПользователей = Новый Соответствие();
+КонецПроцедуры
+
+&ТочкаМаршрута("message") // Обработчик входящего сообщения
+Процедура ВходящееСообщение(Идентификатор, Топик, Сообщение) Экспорт
+
+ // клиент понимает два вида сообщений, которые он отправил сам, и которые отправлены другими клиентами. Они по-разному отображаются на фронте.
+ // Тут мы получаем из кеша имя пользователя по идентификатору соединения и отправляем клиентам.
+
+ ИмяПользователя = КешИменПользователей.Получить(Идентификатор);
+
+ Сообщить(СтрШаблон("Получено сообщение %1 от %2", Сообщение, ИмяПользователя));
+
+ Массив = Новый Массив();
+ Массив.Добавить(Идентификатор);
+
+ ТекстПолучения = ФорматированноеСообщение(ИмяПользователя, Сообщение, Истина);
+ ТекстОтправки = ФорматированноеСообщение(ИмяПользователя, Сообщение, Ложь);
+
+ БрокерСообщенийВебСокетов.ОтправитьСообщениеВсемКроме(Топик, ТекстПолучения, Массив);
+ БрокерСообщенийВебСокетов.ОтправитьСообщениеСписку(Топик, ТекстОтправки, Массив);
+
+КонецПроцедуры
+
+&Отображение("./hwapp/view/chat.html")
+&ТочкаМаршрута("") // точка маршрута, которая отдаем страницу клиента.
+Процедура Главная(Сессия, Ответ) Экспорт
+ // если пользователь не закеширован, перенаправляем на страницу логина.
+ Имя = КешИменПользователей.Получить(Сессия.Идентификатор());
+ Если Имя = Неопределено Тогда
+ Ответ.Перенаправить("/chat/login");
+ КонецЕсли;
+КонецПроцедуры
+
+&ТочкаМаршрута("login") // страница ввода логина.
+&Отображение("./hwapp/view/chatlogin.html")
+Процедура Логин() Экспорт
+КонецПроцедуры
+
+&ТочкаМаршрута("loginprocess") // обработка введенного логина, с кешированием имени.
+Процедура ОбработкаЛогина(ИмяПользователя, Сессия, Ответ) Экспорт
+ Если НЕ ЗначениеЗаполнено(ИмяПользователя) Тогда
+ ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(ТекущаяУниверсальнаяДатаВМиллисекундах());
+ ИмяПользователя = "Noname" + Строка(ГенераторСлучайныхЧисел.СлучайноеЧисло(1, 999));
+ КонецЕсли;
+ Сообщить(СтрШаблон("Регистрация пользователя %1", ИмяПользователя));
+ КешИменПользователей.Вставить(Сессия.Идентификатор(), ИмяПользователя);
+ Ответ.Перенаправить("/chat");
+КонецПроцедуры
+
+&ПриПодключенииВебСокета("/chat/message") // подписка на подключение пользователя
+Процедура ПриПодключенииПользователя(Идентификатор) Экспорт
+ // сообщим всем, что зашел новый пользователь.
+ ИмяПользователя = КешИменПользователей.Получить(Идентификатор);
+
+ Сообщить(СтрШаблон("Подключился %1", ИмяПользователя));
+
+ ТекстСообщенияГостю = ФорматированноеСообщение("Оракул", "Привет " + ИмяПользователя + " !", Истина);
+
+ БрокерСообщенийВебСокетов.ОтправитьСообщениеВсем("/chat/message", ТекстСообщенияГостю);
+КонецПроцедуры
+
+&ПриОтключенииВебСокета("/chat/message") // подписка на отключение пользователя.
+Процедура ПриОтключенииПользователя(Идентификатор) Экспорт
+ // сообщим всем, что пользователь вышел.
+ ИмяПользователя = КешИменПользователей.Получить(Идентификатор);
+
+ Сообщить(СтрШаблон("Отключился %1", ИмяПользователя));
+
+ ТекстСообщения = ФорматированноеСообщение("Оракул", ИмяПользователя + " покинул чат", Истина);
+
+ БрокерСообщенийВебСокетов.ОтправитьСообщениеВсем("/chat/message", ТекстСообщения);
+КонецПроцедуры
+
+Функция ФорматированноеСообщение(Автор, Текст, Получен)
+ ОбъектДляПарсинга = Новый Структура("Author, Time, Text, rcv", Автор, Формат(ТекущаяДата(), "ДФ=ЧЧ:мм"), Текст, Получен);
+
+ Запись = новый ЗаписьJSON;
+ Запись.УстановитьСтроку();
+ ЗаписатьJSON(Запись, ОбъектДляПарсинга);
+
+ Возврат Запись.Закрыть();
+КонецФункции
+```
+
+Вот результат наших трудов.
+
+![ws](../static/winow/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);
+});
+
+```
+
+Полный пример можно посмотреть примерах - [контрол](https://github.com/autumn-library/winow/blob/master/example/hwapp/СерверныеСобытия.os) и [клиент](https://github.com/autumn-library/winow/blob/master/example/hwapp/view/main_sse.html).
+
+Апи объектов:
+
+```БрокерСообщенийВебСокетов```
+
+- ОтправитьСообщениеВсем(Топик, Сообщение): Отправка сообщения всем;
+- ОтправитьСообщениеПоИдСоединения(ИдСоединения, Сообщение): Отправка сообщения конкретному клиенту;
+- ОтправитьСообщениеСписку(Топик, Сообщение, МассивИдентификаторов): Отправка сообщения массиву клиентов;
+- ОтправитьСообщениеВсемКроме(Топик, Сообщение, МассивИсключенийИдентификаторов): Отправка сообщения с исключающим массивом клиентов;
+
+```ТопикиСерверныхСобытий```
+
+- Существует(Топик): Проверка существования топика;
+- Добавить(Топик, ОбработчикОткрытия, ОбработчикЗакрытия): Добавление топика. С возможностью подписки на события открытия и закрытия соединения. Тут принимаются объекты Действие, которые должны иметь интрефейс: ```Процедура ИмяОбработчика(Сессия, ИД) Экспорт``` Где сессия - идентификатор сессии, и идентифкатор конкретного соединения.
+
+```Сообщение```. Получается из фабрики ответов. ```Сообщение = ФабрикаОтветов.СерверноеСобытие();```
+
+- ТипСобытия(ТипСобытия): Установка типа события;
+- ДобавитьСтроку(Строка): Добавление строки в сообщение;
+- Идентификатор(ид) : Установка идентификатора сообщения;
+
+Пример реактивного интерфейса на server sent events
+
+![sse](../static/winow/sse.gif)
+
+## Использование cli
+
+winow предоставляет интерфейс командной строки. Запуск приложения становится еще проще. Для этого нужно установить пакет [winow-cli](https://github.com/autumn-library/winow-cli).
+
+## Контейнеризация
+
+Приложение на winow можно, конечно, запустить в контейнере.
+
+Вот небольшой пример, как это сделать.
+
+Нужно в один каталог положить:
+* Само приложение [app](https://github.com/autumn-library/winow/blob/master/docker/app/), которое содержит скрипт запуска, контролы, файлы и картинки, ```autumn-properties.json``` со всеми настройками и т.д.
+* [Dockerfile](https://github.com/autumn-library/winow/blob/master/docker/Dockerfile) для того, чтобы собрать образ, и прокинуть в него все файлы.
+* Скрипт запуска сервера [docker-entrypoint.sh](https://github.com/autumn-library/winow/blob/master/docker/docker-entrypoint.sh).
+* И все это дело удобно собирать одним скриптом [start.sh](https://github.com/autumn-library/winow/blob/master/docker/start.sh).
\ No newline at end of file