{"id":47142856,"url":"https://github.com/autumn-library/winow","last_synced_at":"2026-03-12T22:16:34.130Z","repository":{"id":50296134,"uuid":"517850634","full_name":"autumn-library/winow","owner":"autumn-library","description":"Минималистичный веб-сервер на нативном OneScript","archived":false,"fork":false,"pushed_at":"2025-12-12T21:57:53.000Z","size":22150,"stargazers_count":82,"open_issues_count":19,"forks_count":18,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-12-14T12:16:56.678Z","etag":null,"topics":["autumn","oscript"],"latest_commit_sha":null,"homepage":"https://autumn-library.github.io/winow/","language":"1C Enterprise","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/autumn-library.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-07-25T23:33:53.000Z","updated_at":"2025-12-12T21:56:46.000Z","dependencies_parsed_at":"2024-06-24T14:45:42.340Z","dependency_job_id":"0fef9fdb-17a9-43a5-9cbf-350a5d184d7d","html_url":"https://github.com/autumn-library/winow","commit_stats":null,"previous_names":[],"tags_count":37,"template":false,"template_full_name":null,"purl":"pkg:github/autumn-library/winow","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/autumn-library%2Fwinow","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/autumn-library%2Fwinow/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/autumn-library%2Fwinow/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/autumn-library%2Fwinow/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/autumn-library","download_url":"https://codeload.github.com/autumn-library/winow/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/autumn-library%2Fwinow/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30446434,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-12T21:31:01.033Z","status":"ssl_error","status_checked_at":"2026-03-12T21:30:43.161Z","response_time":114,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autumn","oscript"],"created_at":"2026-03-12T22:16:33.529Z","updated_at":"2026-03-12T22:16:34.112Z","avatar_url":"https://github.com/autumn-library.png","language":"1C Enterprise","funding_links":[],"categories":[],"sub_categories":[],"readme":"## WINOW is not OneScript.web\n\nМинималистичный веб-сервер, построен на нативном TCPСервер, и работает на желудях. \n\nЗачем это нужно, когда есть OneScript.Web, -CGI и т.д.? Отвечаю - для того, чтобы все было на чистом OneScript! И потому, что могу. С полным контролем, от входа двоичных данных на порт, до определения маршрута, получения данных, генерации ответа по шаблону и отправкой обратно клиенту.\n\n## Disclaimer вашему вниманию\n\nС релиза 0.9.0 добавлена поддержка платформенного объекта `ВебСервер`. А это значит, что при запуске приложения под OneScript версии 2.0 и выше будет использоваться именно он. Возможны артефакты в обработке пост запросов с типом `multipart/form-data`. А так же в этом режиме временно не поддерживаются технологии WebSocket и SSE. Для того, что бы использовать старый механизм под OneScript 2.0 нужно установить настройку `winow.ИспользоватьПрикладнойСервер` в истину.\n\n## Установка \n\n```\nopm install winow\n```\n\n## Книга жалоб и пожеланий !\n\nМожно оставить тут https://github.com/autumn-library/winow/issues или тут https://github.com/oscript-library/winow/issues\n\n## Что можно сделать ?\n\nДанная библиотека позволит Вам достаточно просто подготовить и запустить:\n\n- микросервис, с гибким API\n- быстро сделать МОК для вашего \"любимого\" удаленного API и наконец-то продолжить комфортную разработку.\n- Веб приложение, с отдачей статичных файлов, разграничением доступа по ролям, и генерацией страниц по шаблонам.\n- И все, на что хватит фантазии.\n\n## Какие возможности ?\n\nВ данной библиотеке я постарался реализовать подход в разработке приложений в стиле MVC. \n\nНа текущий момент winow позволяет:\n\n- Обрабатывать входящие GET и POST запросы.\n- Обеспечивать маршрутизацию входящего запроса до нужного метода.\n- Разбирать все входящие параметры.\n- Обрабатывать тело входящего POST запроса.\n- Работать с печеньками (Cookie). \n- Работать с сессиями.\n- Отдавать статичные файлы (картинки, архивы и т.д.)\n- Работать с шаблонами ответов (Синтаксис шаблона чем-то похож на jinja2, но сильно упрощен).\n- Базовая авторизация и управление доступом к страницам по ролям.\n- Использовать протокол WebSocket \n- Использовать протокол server-sent events. (SSE)\n\n## Ограничения ?\n\nДа! Нет никаких обещаний на тему больших нагрузок. И нет поддержки https, погружаться в историю с шифрованием трафика, я еще не готов.\n\n## Как, из чего, зависимости ?\n\nБиблиотека разработана с использованием фреймворка для инверсии зависимостей - https://github.com/autumn-library/autumn. Для более эффективной работы с winow, следует ознакомиться. А так же обязательно пройти по ссылке и поставить звездочку, без этого ничего работать не будет.\n\n## Хеллоу ворлд !\n\nОт слов - к делу. Чтобы понять, как это все работает, давайте сделаем hello-world приложение, которое будет запускаться на localhost:3333 и отвечать простым текстом hello-world. \n\nПервым делом, нам нужна точка входа, которая запустит приложение. \n\nСоздадим такой файл:\n```\nПриветМир.os\n```\n\n```bsl\n#Использовать autumn\n#Использовать winow\n\nПоделка = Новый Поделка;\nПоделка.ЗапуститьПриложение();\n```\n\nТеперь можете запустить файл ```ПриветМир.os``` и ничего не будет работать. И причин для этого ровно две. Первая - вы не сходили https://github.com/autumn-library/autumn и не поставили звезду. Вторая - мы не создали каталог, с классами, которые обрабатывают логику запросов. Я верю, что вы успели сходить и поставить звезду! Перейдем к созданию логики.\n\nСоздаем каталог и файл:\n\n```\napp/КонтролПриветствия.os\n```\n\n```bsl\n\u0026Контроллер(\"/\")\nПроцедура ПриСозданииОбъекта()\n\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"/\")\nПроцедура Приветствие(Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n                                |\u003cdiv\u003e %1 \u003c/div\u003e\",  \"Привет новый дивный мир !\");\n\nКонецПроцедуры\n```\n\nИ снова пробуем запустить ```ПриветМир.os```, и идем в http://localhost:3333/\n\nИ чудо свершилось:\n\n![hw1](docs/hw1.png)\n\n## Передача параметров в строке запроса.\n\nПосле продолжительного восторга, двигаемся дальше. На новом примере разберем по частям, как это работает.\n\nДавайте сделаем еще один контроллер, еще более интерактивный. Сделаем так, что приложение будет нас встречать по имени. Имя мы хотим передавать в параметрах строки запроса.\n\nhttp://localhost:3333/greeter/getparams?name=Nikita\u0026familia=ivanchenko\n\nГде ```greeter``` путь до нашего контроллера. И ```getparams``` точка входа для метода, который обрабатывает запрос. Все что после ```?``` именные параметры.\n\nПоехали, создаем файл:\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\n```bsl\n\u0026Контроллер(\"/greeter\")\nПроцедура ПриСозданииОбъекта()\n\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"getparams\")\nПроцедура Приветствие(Запрос, Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tИмя = Запрос.ПараметрыИменные[\"name\"];\n\tФамилия = Запрос.ПараметрыИменные[\"familia\"];\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Имя: %1 \u003c/div\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Фамилия: %2 \u003c/div\u003e\",  Имя, Фамилия);\n\nКонецПроцедуры\n```\n\nОпять запускаем ```ПриветМир.os```, и идем теперь вот так http://localhost:3333/greeter/getparams?name=Никита\u0026familia=Иванченко\n\n![hw2](docs/hw2.png)\n\nСнова полный успех! Но давайте подробней остановимся на каждом этапе этого чуда.\n\nДопустимо передавать именные параметры по имени в метод точки маршрута\n\n```bsl\n\u0026ТочкаМаршрута(\"getparamsbyname\")\nПроцедура ПроверкаГетПараметровПоИмени(Ответ, ИмяКошки, ИмяСобаки) Экспорт\n\tОтвет.УстановитьТипКонтента(\"txt\");\n\tОтвет.ТелоТекст = СтрШаблон(\"%1 И %2\", \"Кошка=\" + ИмяКошки, \"Собака=\" + ИмяСобаки);\nКонецПроцедуры\n```\n\nФайл, который мы только что сделали, описывает определенную точку в адресной строке. При совпадении с которой перехватывается управление над входящим запросом. Посмотрим поближе.\n\n```bsl\n\u0026Контроллер(\"/greeter\")\nПроцедура ПриСозданииОбъекта()\n\nКонецПроцедуры\n``` \n\nВ начале идет конструктор нашего класса, ```ПриСозданииОбъекта()```. Весь код, который в нем написан, будет выполнен при создании. Удобно тут выполнять всякую инициализацию переменных. \n\nЭтот метод имеет аннотацию ```\u0026Контроллер(\"/greeter\")``` как раз указывает, путь от корня, после которого будет осуществлен перехват.\n\nСтоит отметить что аннотация может быть более длинной, чтобы отвечать логике описания api. Например, ```\u0026Контроллер(\"/app/api/v1/greeter\")``` тоже рабочий вариант, только ходить нужно уже вот сюда http://localhost:3333/app/api/v1/greeter\n\nУ любого контроллера может быть любое множество методов, которыми он обрабатывает входящий запрос.\n\n```bsl\n\u0026ТочкаМаршрута(\"getparams\")\nПроцедура Приветствие(Запрос, Ответ) Экспорт\n``` \n\nДля того, чтобы процедура контроллера могла понимать, что ее вызывают из запроса, ее нужно пометить аннотацией ```\u0026ТочкаМаршрута(\"getparams\")```. Где параметр аннотации указывает имя в пути, после которого ей нужно сработать.\n\nТак же, чтобы все получилось, процедура должна отвечать нескольким требованиям:\n\n- Быть экспортной\n- Принимать на вход параметры, имена которых ограничены и предопределены. Назначение параметров мы разберем по ходу дела.\n\n```Запрос```, например, хранит всю информацию, которая пришла к нам от клиента. В том числе ```Запрос.ПараметрыИменные``` - соответствие, хранящее значения всех параметров, которые переданы после знака ```?```\n\nДальше мы лихо эти параметры читаем.\n\n```bsl\nИмя = Запрос.ПараметрыИменные[\"name\"];\nФамилия = Запрос.ПараметрыИменные[\"familia\"];\n``` \n\nСледующий параметр ```Ответ```, в котором собирается все, что будет отправлено обратно клиенту. Например, вот так:\n\n```bsl\nОтвет.УстановитьТипКонтента(\"html\");\n``` \n\nУстанавливается заголовок ```Content-Type```, благодаря которому браузер понимает, как отобразить то, что мы ему шлем.\n\nПо умолчанию поддерживаются типы:\n\n```bsl\nОписанияТиповРасширений = Новый Соответствие();\nОписанияТиповРасширений.Вставить(\"htm\",\"text/html; charset=utf-8\");\nОписанияТиповРасширений.Вставить(\"html\",\"text/html; charset=utf-8\");\nОписанияТиповРасширений.Вставить(\"css\",\"text/css\");\nОписанияТиповРасширений.Вставить(\"js\",\"text/javascript\");\nОписанияТиповРасширений.Вставить(\"jpg\",\"image/jpeg\");\nОписанияТиповРасширений.Вставить(\"jpeg\",\"image/jpeg\");\nОписанияТиповРасширений.Вставить(\"png\",\"image/png\");\nОписанияТиповРасширений.Вставить(\"gif\",\"image/gif\");\nОписанияТиповРасширений.Вставить(\"ico\",\"image/x-icon\");\nОписанияТиповРасширений.Вставить(\"zip\",\"application/x-compressed\");\nОписанияТиповРасширений.Вставить(\"rar\",\"application/x-compressed\");\nОписанияТиповРасширений.Вставить(\"json\",\"application/json\");\nОписанияТиповРасширений.Вставить(\"txt\",\"text/plain; charset=utf-8\");\n``` \n\nНу и конечно же устанавливаем текст ответа, который вернется клиенту. \n\n```bsl\nОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n                            |\u003cdiv\u003e Имя: %1 \u003c/div\u003e\n                            |\u003cdiv\u003e Фамилия: %2 \u003c/div\u003e\",  Имя, Фамилия);\n``` \n\nСейчас это не удобно и не красиво. Но к концу нашей беседы мы разберемся - как сделать красиво.\n\n## Описание возможных параметров ТочкиМаршрута\n\n* ```Запрос``` - Объект, содержащий все данные о входящем запросе\n\n* ```Ответ``` - Объект, содержащий все данные об ответе, который будет отправлен пользователю\n\n* ```Сессия``` - Объект, хранящий сессионные данные пользователя. Имеет поля ```Данные```, соответствие для хранения любых данных бизнес-логики и ```Логин```, строковое имя пользователя, после авторизации.\n\nТак же, для удобства, можно получать части объектов ```Сессия``` и ```Запрос```.\n\n* ```Логин``` - Логин, хранящейся в сессии. Аналог ```Сессия.Логин```.\n\n* ```ДанныеСессии``` - Соответствие с данными, хранящееся в сессии. Аналог ```Сессия.Данные```.\n\n* ```ТекстЗапроса``` - Полный текст входящего запроса.\n\n* ```ЗаголовкиЗапроса``` - Соответствие заголовков запроса.\n\n* ```ТелоЗапроса``` - Текст тела запроса.\n\n* ```ТелоЗапросаСоответствие``` - Соответствие полей тела запроса.\n\n* ```ТелоЗапросаДвоичныеДанные``` - Тело запроса в виде двоичных данных.\n\n* ```МетодЗапроса``` - Метод (GET, POST, PUT и тд).\n\n* ```ПолныйПутьЗапроса``` - Полный путь запроса.\n\n* ```ПутьЗапроса``` - Путь без параметров.\n\n* ```ПараметрыЗапросаИменные``` - Соответствие, с именными параметрами.\n\n* ```ПараметрыЗапросаПорядковые``` - Массив с запросами.\n\n* ```ДатаПолученияЗапроса``` - Время получения запроса.\n\n* ```ДвоичныеДанныеЗапроса``` - Двоичные данные всего запроса.\n\n* ```КукиЗапроса``` - Куки.\n\n* ```АдресУдаленногоУзла``` - Адрес удаленного узла.\n\n* ```ПортУдаленногоУзла``` - Порт удаленного узла.\n\n*  ```ТелоЗапросаОбъект``` - Тело запроса, сериализованное из JSON. (При наличии заголовка ```\"Content-Type:application/json\"```). Укажите аннотацию параметра ```\u0026Тип(\"ИмяКласса\")``` для десериализации в нужный тип.\n\n## Еще один способ передачи параметров в строке запроса. \n\nПредыдущий пример показал, как можно передать параметры в строке запроса, при этом параметры имели имена. Теперь рассмотрим пример, когда параметры упорядоченные.\n\nДавайте сделаем наконец калькулятор! И будет он работать вот так:\n\n```\nhttp://localhost:3333/greeter/calc/\u003coperation\u003e/\u003cfirst\u003e/\u003csecond\u003e/\n```\nГде ```calc``` - точка маршрута. ```operation``` - вид операции, будем поддерживать minus и plus. и следом два слагаемых нашего уравнения.\n\nДобавим в наш\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\nновую точку маршрута:\n\n```bsl\n\u0026ТочкаМаршрута(\"calc\")\nПроцедура Калькулятор(ПараметрыЗапросаПорядковые, Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tЕсли ПараметрыЗапросаПорядковые.Количество() \u003c\u003e 3 Тогда\n\n\t\tРешение = \"Неверное число параметров\";\n\n\tИначеЕсли Не (ПараметрыЗапросаПорядковые[0] = \"minus\" \n\t\t\t\tИ Не\tПараметрыЗапросаПорядковые[0] = \"plus\") Тогда\n\t\tРешение = \"Операция не распознана\";\n\n\tИначе\n\t\tПопытка\n\t\t\tЧисло1 = Число(ПараметрыЗапросаПорядковые[1]);\n\t\t\tЧисло2 = Число(ПараметрыЗапросаПорядковые[2]);\n\n\t\t\tЕсли ПараметрыЗапросаПорядковые[0] = \"minus\" Тогда\n\t\t\t\tРешение = Число1 - Число2;\n\t\t\tИначе\n\t\t\t\tРешение = Число1 + Число2;\n\t\t\tКонецЕсли\n\n\t\tИсключение\n\t\t\tРешение = \"Ошибка конвертации в число\"\n\t\tКонецПопытки;\n\tКонецЕсли;\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Ответ: %1 \u003c/div\u003e\",  Решение);\n\nКонецПроцедуры\n``` \n\nПерезапустим приложение, и перейдем по ссылке http://localhost:3333/greeter/calc/plus/3/2\n\nИ в ответ перед нами будет красоваться \n\n![hw3](docs/hw3.png)\n\nНа самом деле, тут все очень просто. Когда мы объявляем точку маршрута ```\u0026ТочкаМаршрута(\"calc\")```, все что дальше в пути через ```/``` будет любезно складываться в массив ```ПараметрыЗапросаПорядковые```. А что делать с массивами, вы и без меня знаете.\n\n## Шаблоны параметров в строке запроса\n\nТак же есть возможность задавать шаблон строки маршрута, где можно задавать именные параметры. \n\nНапример: \n\n```bsl\n\u0026ТочкаМаршрута(\"calc/{Число1}/multiply/{Число2}\")\nПроцедура ШаблонныеПараметрыУмножение(Ответ, Число1, Число2) Экспорт\n\tОтвет.УстановитьТипКонтента(\"txt\");\n\tОтвет.ТелоТекст = Число(Число1) * Число(Число2);\nКонецПроцедуры\n```\n\nВ точке маршрута фигурными скобками указываем параметры ```Число1``` и ```Число2```,  и эти параметры будут переданы в метод обработчик во время выполнения запроса.\n\n## Входящие POST запросы\n\nС пост запросами, все почти так же просто. ```Запрос``` Имеет два поля ```Тело``` и ```ТелоДвоичныеДанные```, т.к. пользователь может закинуть нам как текст, так и картинку например. \n\nДавайте потренируемся в обработке таких запросов, усовершенствуем наше приложение и научим его возводить в степень переданное число. \n\nВводить число мы будем по адресу http://localhost:3333/greeter/inputstepen, где будет форма ввода числа, и кнопка расчета. После расчета мы будем перенаправлены на http://localhost:3333/greeter/resultstepen. Форма будет передавать параметры методом POST.\n\nДля реализации этой задумки добавим в наш\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\nэтот код, с двумя новыми точками маршрута\n\n```bsl\n\u0026ТочкаМаршрута(\"inputstepen\")\nПроцедура ВводСтепени(Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tОтвет.ТелоТекст = \n\t\"\u003cform method=\"\"post\"\" action=\"\"/greeter/resultstepen\"\"\u003e\n\t|\u003clabel for=\"\"chislo\"\"\u003eВведи число:\u003c/label\u003e\u003cbr\u003e\n\t|\u003cinput type=\"\"text\"\" id=\"\"chislo\"\" name=\"\"chislo\"\"\u003e\u003cbr\u003e\n\t|\u003clabel for=\"\"stepen\"\"\u003eВведи степень:\u003c/label\u003e\u003cbr\u003e\n\t|\u003cinput type=\"\"text\"\" id=\"\"stepen\"\" name=\"\"stepen\"\"\u003e\u003cbr\u003e\u003cbr\u003e\n\t|\u003cinput type=\"\"submit\"\" value=\"\"Посчитать\"\"\u003e\n\t|\u003c/form\u003e \";\n\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"resultstepen\")\nПроцедура ВозводительВСтепень(Запрос, Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tПостПараметры = Парсеры.ПараметрыИзТекста(Запрос.Тело);\n\n\tПопытка\n\t\tРешение = Pow(ПостПараметры[\"chislo\"], ПостПараметры[\"stepen\"]);\t\n\tИсключение\n\t\tРешение = \"Ошибка при расчетах \" + ОписаниеОшибки();\n\tКонецПопытки;\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Ответ: %1 \u003c/div\u003e\",  Решение);\n\nКонецПроцедуры\n``` \n\n![hw4](docs/hw4.png)\n\n![hw5](docs/hw5.png)\n\nТеперь разберемся, что тут произошло. Не буду останавливаться на описании HTML тегов, для этого в интернете сайтов больше, чем звезд на небе. \n\nТочка маршрута ```inputstepen``` показала нам форму, которая при расчете перенаправляет нас на ```resultstepen``` и в теле запроса передает параметры формы, которые имеют вид ```chislo=2\u0026stepen=3```. Все что нам осталось, это обработать запрос.\n\nМы можем парсить самостоятельно, но можно внедрить объект ```Парсеры```, который умеет парсить параметры в таком формате ```Парсеры.ПараметрыИзТекста(\u003cСтрокаСПараметрами\u003e)```. Этот метод вернет соответствие со значениями, которые в последствии нужно правильно использовать.\n\nЕсли во входящем запросе придет заголовок ```\"Content-Type:application/json\"``` тогда его тело автоматом будет распарсено в структуру ```ТелоЗапросаОбъект``` с которой можно работать.\n\nЕсли вам нужна десериализация тела запроса сразу в экземпляр нужного класса (типизированная модель), добавьте к параметру ```ТелоЗапросаОбъект``` аннотацию ```\u0026Тип(\"ИмяТипа\")```, где ИмяТипа - это имя требуемого типа. Поддерживаются пользовательские классы, размеченные по правилам библиотеки [jason](https://github.com/nixel2007/jason).\n\n```bsl\n\u0026ТочкаМаршрута(\"postjsonbody\")\nПроцедура ПроверкаПостЗапросаКакОбъект(Ответ, ТелоЗапросаОбъект, ЗаголовкиЗапроса) Экспорт\n\tОтвет.УстановитьТипКонтента(\"txt\");\n\tОтвет.ТелоТекст = СтрШаблон(\"%1 %2\", ТелоЗапросаОбъект.Имя, ТелоЗапросаОбъект.Фамилия);\nКонецПроцедуры\n```\n\nПример типизированного тела с ```ТелоЗапросаОбъект```:\n\n```bsl\n// Класс вашей модели, на который будет маппиться JSON\n// Имя типа должно совпадать с указанным в \u0026Тип(\"...\")\n// Например: Имя модуля файла в src/Классы/ — \"МояМодель.os\" =\u003e Тип(\"МояМодель\")\n\n\u0026ТочкаМаршрута(\"postjsonbodytyped\")\nПроцедура ПринятьТипизированноеТело(Ответ, \u0026Тип(\"МояМодель\") ТелоЗапросаОбъект) Экспорт\n\tОтвет.УстановитьТипКонтента(\"txt\");\n\tОтвет.ТелоТекст = СтрШаблон(\"%1 %2\", ТелоЗапросаОбъект.Имя, ТелоЗапросаОбъект.Фамилия);\nКонецПроцедуры\n```\n\nЕсли во входящем запросе придет заголовок ```\"Content-Type:application/x-www-form-urlencoded\"``` тогда его тело автоматом будет распарсено по именам и значениям, которые будет принимать метод точки маршрута\n\n```bsl\n\u0026ТочкаМаршрута(\"postformbody\")\nПроцедура ПроверкаПостЗапросаКакФорма(Ответ, Имя, Фамилия) Экспорт\n\tОтвет.УстановитьТипКонтента(\"txt\");\n\tОтвет.ТелоТекст = СтрШаблон(\"%1 %2\", Имя, Фамилия);\nКонецПроцедуры\n```\n\n## Работа с куками\n\nКуки, это возможность сохранить на клиенте, в браузере, какую-либо информацию. \n\nОбъекты ```Запрос``` и ```Ответ```, которые мы получаем в метод, который мы помечаем как ```ТочкаМаршрута```. Оба этих объекта имеют свойство ```Куки```. Соответственно во входящем запросе их можно читать, а в ответе устанавливать.\n\nМодернизируем файл\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\n```bsl\n\u0026ТочкаМаршрута(\"setcookie\")\nПроцедура УстановитьКуку(Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tИмяКуки = \"ДатаПоследнегоВхода\";\n\tЗначениеКуки = ТекущаяДата();\n\n\tНоваяКука = Ответ.Куки.Добавить(ИмяКуки, ЗначениеКуки);\n\n\tОтвет.ТелоТекст = \"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t|\u003cdiv\u003e Кука установлена \u003c/div\u003e\";\n\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"readcookie\")\nПроцедура ПрочитатьКуку(Запрос, Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tИмяКуки = \"ДатаПоследнегоВхода\";\n\n\tЗначениеКуки = Запрос.Куки.ПолучитьЗначениеПоИмени(ИмяКуки);\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Кука: %1 \u003c/div\u003e\", ЗначениеКуки);\n\nКонецПроцедуры\n``` \n\n![hw6](docs/hw6.png)\n\n![hw7](docs/hw7.png)\n\nЕще раз, не забываем, что куки хранятся на стороне браузера.\n\n## Хранение данных сессии\n\nЕще один параметр точки маршрута ```Сессия``` имеет поле ```Данные```. По сути, это соответствие, в которое можно записывать и читать любые значения. Эти данные хранятся на сервере, пока он работает. При остановке, данные сессии пропадают. При необходимости, в рамках приложения, можно дописать хранение данных сессии в файлах, базах данных и т.д.\n\nЕще один пример\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\n```bsl\n\u0026ТочкаМаршрута(\"setsessiondata\")\nПроцедура УстановитьДанныеСессии(Ответ, Сессия) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tИмяПараметраСессии = \"ДатаПоследнегоВхода\";\n\tЗначениеПараметраСессии  = ТекущаяДата();\n\n\t\n\tСессия.Данные[ИмяПараметраСессии] = ЗначениеПараметраСессии;\n\n\tОтвет.ТелоТекст = \"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t|\u003cdiv\u003e Данные сессии установлены \u003c/div\u003e\";\n\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"readsessiondata\")\nПроцедура ПрочитатьДанныеСессии(Ответ, Сессия) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tИмяПараметраСессии = \"ДатаПоследнегоВхода\";\n\tЗначениеПараметраСессии  = Сессия.Данные[ИмяПараметраСессии];\n\n\tОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t\t|\u003cdiv\u003e Значение параметра сессии: %1 \u003c/div\u003e\", ЗначениеПараметраСессии);\n\nКонецПроцедуры\n``` \n\n![hw8](docs/hw8.png)\n\n![hw9](docs/hw9.png)\n\n## Публикация статичных файлов.\n\nЧасто нужно открывать доступ для скачивания всевозможных файлов. Таких как картинки, js-скрипты, css и т.д.\n\nДля этого нужно сконфигурировать сервер, указав в файле ```autumn-properties.json``` нужные параметры. Этот файл нужно положить рядом с ```ПриветМир.os```\n\n```\nautumn-properties.json\n```\n```JSON\n{ \"winow\":\n  {\n    \"КаталогиСФайлами\": {\n      \"/images\": \"./app/files\"\n   }\n  }\n}\n```\n\nПро конфигурирование через этот файл расскажу ниже, а пока давайте попробуем сделать наше приложение повеселее и добавить картинок.\n\nДобавим в каталог приложения пару картинок.\n\n```\napp/files/zl1.jpg\napp/files/fun/zl2.jpg\n```\n\n![hw10](docs/hw10.png)\n\nКак видим, по заданному пути теперь доступны файлы из каталога, при чем с сохранением внутренней иерархии каталога файлов.\n\nСтоит отметить, что доступны становятся не все файлы сразу, а только те, расширения которых описаны в соответствии\n\n```bsl\nОписанияТиповРасширений = Новый Соответствие();\nОписанияТиповРасширений.Вставить(\"htm\",\"text/html; charset=utf-8\");\nОписанияТиповРасширений.Вставить(\"html\",\"text/html; charset=utf-8\");\nОписанияТиповРасширений.Вставить(\"css\",\"text/css\");\nОписанияТиповРасширений.Вставить(\"js\",\"text/javascript\");\nОписанияТиповРасширений.Вставить(\"jpg\",\"image/jpeg\");\nОписанияТиповРасширений.Вставить(\"jpeg\",\"image/jpeg\");\nОписанияТиповРасширений.Вставить(\"png\",\"image/png\");\nОписанияТиповРасширений.Вставить(\"gif\",\"image/gif\");\nОписанияТиповРасширений.Вставить(\"ico\",\"image/x-icon\");\nОписанияТиповРасширений.Вставить(\"zip\",\"application/x-compressed\");\nОписанияТиповРасширений.Вставить(\"rar\",\"application/x-compressed\");\nОписанияТиповРасширений.Вставить(\"json\",\"application/json\");\nОписанияТиповРасширений.Вставить(\"txt\",\"text/plain; charset=utf-8\");\n``` \n\nПри желании этот список можно расширить.\n\n## Работа с шаблонами страниц.\n\nЕсли вы дочитали до этого пункта, я в первую очередь Вам благодарен. И в знак уважения, расскажу про механизм шаблонов. Я ведь раньше гордо заявил, что тут возможен подход MVC, так вот вы, наверное, все время задавались вопросом, где же V? И правда, писать код так:\n\n```bsl\nОтвет.ТелоТекст = СтрШаблон(\"\u003c!DOCTYPE html\u003e\n\t\t\t\t\t\t\t|\u003cdiv\u003e Имя: %1 \u003c/div\u003e\n\t\t\t\t\t\t\t|\u003cdiv\u003e Фамилия: %2 \u003c/div\u003e\",  Имя, Фамилия);\n``` \n\nпросто не удобно, и мало приличных слов для такого подхода можно подобрать, и ни в одном не будет буквы V. Но у меня есть решение!\n\nСразу покажу пример, а потом разберем по строчкам. Давайте отобразим страницу, на которой выведем текущее время, совершенно псевдослучайное число, динамически выведем случайное количество строк, и попробуем поиграться с условиями.\n\nПоехали!\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\n```bsl\n\u0026Контроллер(\"/demoviews\")\nПроцедура ПриСозданииОбъекта()\n\nКонецПроцедуры\n\n\u0026Отображение(\"./app/view/view1.html\")\n\u0026ТочкаМаршрута(\"demo1\")\nПроцедура ДемонстрацияОтображения(Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tГСЧ = Новый ГенераторСлучайныхЧисел();\n\t\n\tСлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 10);\n\n\tМассив = Новый Массив();\n\n\tДля Сч = 1 по СлучайноеЧисло Цикл\n\t\tМассив.Добавить(Строка(Новый УникальныйИдентификатор()));\n\tКонецЦикла;\n\n\tМодель = Новый Структура();\n\tМодель.Вставить(\"СлучайноеЧисло\", СлучайноеЧисло);\n\tМодель.Вставить(\"МассивСтрок\", Массив);\n\n\tОтвет.Модель = Модель;\n\nКонецПроцедуры\n``` \n\nЧто тут нового? Во первых у точки маршрута появилась аннотация ```\u0026Отображение(\"./app/view/view1.html\")```. А во вторых - определяется структура и устанавливается в ```Ответ.Модель```. В этом весь секрет. После работы метода, на сцену выходит шаблонизатор, найдет указанный шаблон и разложит данные из модели, в соответствии с разметкой.\n\n```\napp/view/view1.html\n```\n\n\u003c!-- {% raw %} --\u003e\n\n```html\n\u003c!doctype html\u003e\n    \u003chtml\u003e\n    \u003chead\u003e\n    \u003ctitle\u003eДемонстрация работы отображений\u003c/title\u003e\n   \u003c/head\u003e\n    \u003cbody\u003e\n    \n    \u003cdiv\u003eПривет! Это отображение из шаблона. Точное время {{ ТекущаяДата() }} \n        \u003cbr\u003e\n        \u003cdiv\u003eТы это не увидишь, но тут объявляются переменные\u003c/div\u003e\n            {%\n            ОднаПеременная = 1;\n            ВтораяПеременная = \"Секрет\";\n            %}\n    \u003c/div\u003e\n    \u003cdiv\u003e Вот твое случайное число {{ Модель.СлучайноеЧисло }} \u003c/div\u003e\n\n    {% Если Модель.СлучайноеЧисло \u003e 5 Тогда %}\n\n        \u003cdiv\u003eСлучайное число БОЛЬШЕ пяти\u003c/div\u003e\n\n    {% Иначе %}\n\n        \u003cdiv\u003eСлучайное число НЕ больше пяти\u003c/div\u003e\n\n    {% КонецЕсли; %}\n\n    \u003cdiv\u003eДавай выведу строки из массива:\u003c/div\u003e\n\n    {% Для Каждого СтрокаИзМассива из Модель.МассивСтрок Цикл %}\n\n        \u003cdiv\u003eЗначение строки: {{ СтрокаИзМассива }} и оно достаточно случайно\u003c/div\u003e\n\n    {% КонецЦикла; %}\n\n    \u003cdiv\u003eРанее я объявил переменные, теперь покажу их\u003c/div\u003e\n    \u003cdiv\u003eОднаПеременная = {{ ОднаПеременная }}\u003c/div\u003e\n    \u003cdiv\u003eВтораяПеременная = {{ ВтораяПеременная }}\u003c/div\u003e\n\n    \u003c/body\u003e\n    \u003c/html\u003e\n``` \n\n\u003c!-- {% endraw %} --\u003e\n\nЕсли присмотреться, то шаблон это просто HTML разметка, которую смешали с 1сным кодом. Вот это коктейль получился!\n\nОсновные принципы разметки:\n\nВыражения - обозначаются тегами. ```{{ \u003cВыражение\u003e }}```. Тут может быть: \n\n- Любое выражение на 1С, которое возвращает значение ```{{ 1 + 3 }}```\n- Переменная ```{{ Модель.ЛюбоеЗначение }}```\n- Функция ```{{ Макс(1,5,9,7) }}```\n\n\u003c!-- {% raw %} --\u003e\n\nОператоры - обозначаются тегами. ```{% \u003cКодНа1С\u003e %}```.\n\n\u003c!-- {% endraw %} --\u003e\n\nЭто полноценный код на 1С. Можно объявлять переменные, взаимодействовать с ```Модель```, использовать управляющие блоки(Циклы, Условия)\n\n![hw11](docs/hw11.png)\n\n## Обработчики шаблонов.\n\nМожет быть так, что до или после рендера модели в шаблоне, нужно выполнить некие манипуляции с текстом шаблона. Для выполнения этой операции нужно зарегистрировать обработчики событий до рендера и после. Например:\n\n\u003c!-- {% raw %} --\u003e\n\n```html\n\u003cdiv\u003e\n@ТекстЗаменыДоРендера@\n{{Модель}}\n@ТекстЗаменыПослеРендера@\n\u003c/div\u003e\n```\n\n\u003c!-- {% endraw %} --\u003e\n\nВ этом шаблоне, мы хотим заменить вставки, неким текстом. Для этого добавим поделку два желудя.\n\n```bsl\n\u0026Желудь\n\u0026Прозвище(\"ПередОбработкойОтображения\")\nПроцедура ПриСозданииОбъекта()\n\t\nКонецПроцедуры\n\nПроцедура Преобразовать(ТекстШаблона) Экспорт\n\tТекстШаблона = СтрЗаменить(ТекстШаблона, \"@ТекстЗаменыДоРендера@\", \"Шапка\");\nКонецПроцедуры\n```\n\n```bsl\n\u0026Желудь\n\u0026Прозвище(\"ПослеОбработкиОтображения\")\nПроцедура ПриСозданииОбъекта()\n\t\nКонецПроцедуры\n\nПроцедура Преобразовать(ТекстШаблона) Экспорт\n\tТекстШаблона = СтрЗаменить(ТекстШаблона, \"@ТекстЗаменыПослеРендера@\", \"Подвал\");\nКонецПроцедуры\n```\n\nТут мы добавили желуди с ```Прозвище``` \"ПередОбработкойОтображения\" и \"ПослеОбработкиОтображения\". Таких желудей может быть несколько. Но у каждого такого желудя должна быть процедура с именем ``Преобразовать```, в которую будет передан текст шаблона.\n\n## Компоненты. \n\n\u003c!-- {% raw %} --\u003e\n\nПисать шаблоны круто, но что может быть еще круче? Писать меньше шаблонов, и переиспользовать уже имеющиеся. Представим, что вам в разных местах нужно отображать одну и туже информацию, (таблицы, элементы меню, и т.д.). для решения этой задачи, шаблон имеет секретную функцию ```{{ ВывестиПоШаблону(\u003cПуть до шаблона\u003e, \u003cМодель для шаблона\u003e) }}```\n\n\u003c!-- {% endraw %} --\u003e\n\nДавайте покажу, как это работает\n\n```\napp/ИнтерактивныйКонтролПриветствия.os\n```\n\n```bsl\n\u0026Отображение(\"./app/view/view1.html\")\n\u0026ТочкаМаршрута(\"demo1\")\nПроцедура ДемонстрацияОтображения(Ответ) Экспорт\n\n\tОтвет.УстановитьТипКонтента(\"html\");\n\n\tГСЧ = Новый ГенераторСлучайныхЧисел();\n\t\n\tСлучайноеЧисло = ГСЧ.СлучайноеЧисло(1, 10);\n\n\tМассив = Новый Массив();\n\n\tДля Сч = 1 по СлучайноеЧисло Цикл\n\t\tМассив.Добавить(Строка(Новый УникальныйИдентификатор()));\n\tКонецЦикла;\n\n\tМодель = Новый Структура();\n\tМодель.Вставить(\"СлучайноеЧисло\", СлучайноеЧисло);\n\tМодель.Вставить(\"МассивСтрок\", Массив);\n\n\t// добавим в модель второй массив\n\tМассивФруктов = Новый Массив();\n\tМассивФруктов.Добавить(\"Яблоко\");\n\tМассивФруктов.Добавить(\"Апельсин\");\n\tМассивФруктов.Добавить(\"Банан\");\n\tМассивФруктов.Добавить(\"Желудь\");\n\n\tМодель.Вставить(\"ВторойМассив\", МассивФруктов);\n\n\tОтвет.Модель = Модель;\n\nКонецПроцедуры\n``` \n\nШаблоны:\n\n```\napp/view/view1.html\n```\n\n\u003c!-- {% raw %} --\u003e\n\n```html\n    \u003c!doctype html\u003e\n    \u003chtml\u003e\n    \u003chead\u003e\n    \u003ctitle\u003eДемонстрация работы отображений\u003c/title\u003e\n   \u003c/head\u003e\n    \u003cbody\u003e\n   \n    \n    \u003cdiv\u003eПривет! Это отображение из шаблона. Точное время {{ ТекущаяДата() }} \n        \u003cbr\u003e\n        \u003cdiv\u003eТы это не увидишь, но тут объявляются переменные\u003c/div\u003e\n            {%\n            ОднаПеременная = 1;\n            ВтораяПеременная = \"Секрет\";\n            %}\n    \u003c/div\u003e\n    \u003cdiv\u003e Вот твое случайное число {{ Модель.СлучайноеЧисло }} \u003c/div\u003e\n\n    {% Если Модель.СлучайноеЧисло \u003e 5 Тогда %}\n\n        \u003cdiv\u003eСлучайное число БОЛЬШЕ пяти\u003c/div\u003e\n\n    {% Иначе %}\n\n        \u003cdiv\u003eСлучайное число НЕ больше пяти\u003c/div\u003e\n\n    {% КонецЕсли; %}\n\n    \u003cbr\u003e\n    \u003cdiv\u003eДавай выведу строки из массива:\u003c/div\u003e\n\n    {{ ВывестиПоШаблону(\"./app/view/printarray.html\", Модель.МассивСтрок) }}\n    \n    \u003cbr\u003e\n    \u003cdiv\u003eДавай выведу строки из второго массива:\u003c/div\u003e\n\n    {{ ВывестиПоШаблону(\"./app/view/printarray.html\", Модель.ВторойМассив) }}\n\n    \u003cbr\u003e\n    \u003cdiv\u003eРанее я объявил переменные, теперь покажу их\u003c/div\u003e\n    \u003cdiv\u003eОднаПеременная = {{ ОднаПеременная }}\u003c/div\u003e\n    \u003cdiv\u003eВтораяПеременная = {{ ВтораяПеременная }}\u003c/div\u003e\n\n    \u003c/body\u003e\n    \u003c/html\u003e\n```\n\n\u003c!-- {% endraw %} --\u003e\n\n```\napp/view/printarray.html\n```\n\n\u003c!-- {% raw %} --\u003e\n\n```html\n\u003cdiv\u003e\n{% Для Каждого СтрокаИзМассива из Модель Цикл %}\n\n    \u003cdiv\u003eЗначение строки: {{ СтрокаИзМассива }}\u003c/div\u003e\n\n{% КонецЦикла; %}\n\u003c/div\u003e\n```\n\n\u003c!-- {% endraw %} --\u003e\n\n![hw12](docs/hw12.png)\n\n## Общее отображение контрола.\n\nДля удобства разработки веб приложения хочется разделить отображения, и добавить что-то общее для всех точек маршрута. Например, общая html разметка, с заголовками, меню, подвалом и тд. Для этих целей есть возможность с помощью аннотации в конструкторе контрола указать общий шаблон. \n\n```bsl\n\u0026Контроллер(\"/demoviews\")\n\u0026Отображение(Шаблон = \"./hwapp/view/main.html\", Метод = \"ПолучитьМодельКонтрола\")\nПроцедура ПриСозданииОбъекта()\n\nКонецПроцедуры\n\nФункция ПолучитьМодельКонтрола(Запрос) Экспорт\n\tМодель = Новый Структура(\"Заголовок, Дата\", \"Демонстрация работы отображений\", Запрос.ДатаПолучения);\n\n\tВозврат Модель;\nКонецФункции\n```\n\nГде ```\u0026Отображение(Шаблон = \"./hwapp/view/main.html\", Метод = \"ПолучитьМодельКонтрола\")``` аннотация, указывает где расположен шаблон, и каким методом для него формируется модель с данными. Параметры этого метода так же могут быть выбраны, аналогично методам точек маршрута.\n\nА вот так выглядит общий шаблон\n\n\u003c!-- {% raw %} --\u003e\n\n```html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n\u003ctitle\u003e{{Модель.Заголовок}}\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003cdiv\u003eШапка страницы! Дата получения запроса: {{Модель.Дата}}\u003c/div\u003e\n\n@Контент\n\n\u003cdiv\u003eПодвал страницы\u003c/div\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n\u003c!-- {% endraw %} --\u003e\n\nГде тег ```@Контент``` будет заменен результатом ответа точки маршрута.\n\nОднако бывают ситуации, когда у контроллера есть отображение, но какая точка маршрута должна возвращать ответ, без его применения. В такой ситуации, для метода точки маршрута нужно добавить аннотацию ```\u0026НеВыводитОтображениеКонтроллера```. Например, вот так:\n\n```bsl\n\u0026Отображение(\"./app/view/view1.html\")\n\u0026ТочкаМаршрута(\"demo2\")\n\u0026НеВыводитОтображениеКонтроллера\nПроцедура ДемонстрацияОтображенияБезОбщегоОтображения(Ответ) Экспорт\n\t...\nКонецПроцедуры\n```\n\n\n# Ответы по ошибкам.\n\nОбрабатывая входящие запросы, могут случиться исключения. Ну кто с первого раза напишет правильно код? Сервер при исключении вернет страницу с кодом 500. Шаблон этой страницы можно переопределить. Моделью там будет структура\n\n```bsl\nОтвет.Модель = Новый Структура();\nОтвет.Модель.Вставить(\"КодСостояния\", 500);\nОтвет.Модель.Вставить(\"ТекстСообщения\", ТекстОшибки);\nОтвет.Модель.Вставить(\"Запрос\", Запрос);\n```\n\nНу а какой пользователь с первого раза введет без ошибки адрес ресурса? Сервер вернет ему 404. Шаблон этой ошибки так же можно переопределить. Модель там следующая\n\n```bsl\nОтвет.Модель = Новый Структура();\nОтвет.Модель.Вставить(\"КодСостояния\", 404);\nОтвет.Модель.Вставить(\"ТекстСообщения\", \"Страница не найдена\");\nОтвет.Модель.Вставить(\"Запрос\", Запрос);\n```\n\nА вот таким не замысловатым способом можно переопределить шаблон стандартной ошибки\n\n```\napp/КонтролПриветствия.os\n```\n\n\u003c!-- {% raw %} --\u003e\n\n```bsl\n\u0026ФинальныйШтрих\nПроцедура ПостИнициализация() Экспорт\n\tОбщийКонтейнер.МенеджерОтображений.УстановитьШаблон404(\"\u003c!DOCTYPE html\u003e\n\t\t\t|\u003cdiv\u003e\u003ch1\u003e {{ Модель.КодСостояния }} \u003c/h1\u003e\u003c/div\u003e\n\t\t\t|\u003cdiv\u003e {{ Модель.ТекстСообщения }} \u003c/div\u003e\n\t\t\t|\u003cdiv\u003e Искомый ресурс {{ Модель.Запрос.Путь }} не найден \u003c/div\u003e\");\nКонецПроцедуры\n```\n\n\u003c!-- {% endraw %} --\u003e\n\n# Перенаправление\n\nИногда бывает так, что нужно с одной страницы, перенаправить позльзователя на другую.\n\nДля этого, у объекта ```Ответ``` есть метод ```Перенаправить(\u003cАдрес куда перенаправить\u003e)```. Например, подобная точка маршрута будет перенаправлять запрос в корень приложения\n\n```bsl\n\u0026ТочкаМаршрута(\"/redir\")\nПроцедура Перенаправление(Ответ) Экспорт\n\tОтвет.Перенаправить(\"/\");\nКонецПроцедуры\n```\n\n# Загрузка настроек из файла\n\nНастройки порта, имени хоста, каталогов файлов и приложений можно можно хранить в json файле, и загружать при старте приложения.\n\nФайл обязательно должен называться ```autumn-properties.json``` / ```autumn-properties.yml``` / ```autumn-properties.yaml```, и быть в корне запуска сервера или в подкаталоге ```src/```. Если какие-то значения не указаны, то они имеют значения по умолчанию.\n\nПример:\n\n```JSON\n{ \"winow\":\n  {\n      \"Порт\": 3331, // по умолчанию 3333 \n\t  \"АвтоСтарт\": true, // Управляет автоматическим запуском при Поделка.ЗапуститьПриложение() (по умолчанию Истина)\n      \"ИмяХоста\": \"MySuperAPPHost\", // по умолчанию localhost \n      \"КаталогСПриложениями\": \"./hwapp\", // по умолчанию \"./app\"\n\t  \"РазмерБуфера\": 1024, // Размер порции в байтах, которыми читаются данные из TCP соединения. (по умолчанию 1024)\n      \"КаталогиСФайлами\": {\n         \"/images\": \"./hwapp/files\" // значения по умолчанию нет\n      }\n  }\n}\n```\n\n# Управление доступом\n\nДля управления доступом к точке маршрута, предусмотрена аннотация ```\u0026Роли(\"\u003cСписок ролей через запятую\u003e\")```. Все очень просто, и остается ответить только на один вопрос - как эти роли раздать, и как хранить данные входа пользователей. Пока это mvp, точного ответа не дам. Разработчик может самостоятельно придумать, как и где хранить группы и пароли. Я только покажу, как их подключить в наше приложение.\n\n```\napp/КонтролСУправлениемДоступом.os\n```\n\n```bsl\n\u0026Пластилин\nПерем МенеджерДоступа Экспорт;\n\n\u0026Контроллер(\"/sec\")\nПроцедура ПриСозданииОбъекта()\n\t\nКонецПроцедуры\n\n\u0026ФинальныйШтрих\nПроцедура ПроинициализироватьРоли() Экспорт \n\t\n\t// Инициализация данных входа \"пользователей\"\n\tМенеджерДоступа.ДобавитьТокен(\"Админ\", \"123\");\t\n\tМенеджерДоступа.ДобавитьТокен(\"Пользователь\", \"111\");\n\n\t// Назначение ролей \"пользователям\"\n\tМенеджерДоступа.ДобавитьРольЛогина(\"Админ\", \"Администраторы\");\n\tМенеджерДоступа.ДобавитьРольЛогина(\"Админ\", \"Пользователи\");\n\n\tМенеджерДоступа.ДобавитьРольЛогина(\"Пользователь\", \"Пользователи\");\nКонецПроцедуры\n\n// Точка доступная роли пользователи\n\u0026Роли(\"Пользователи\")\n\u0026ТочкаМаршрута(\"user\")\nПроцедура Пользователь(Ответ) Экспорт\n\tОтвет.ТелоТекст = \"Пользователи\";\nКонецПроцедуры\n\n// Точка доступная роли Администраторы\n\u0026Роли(\"Администраторы\")\n\u0026ТочкаМаршрута(\"admin\")\nПроцедура Админ(Ответ) Экспорт\n\tОтвет.ТелоТекст = \"Админка\";\nКонецПроцедуры\n\n// обычная точка, доступная всем\n\u0026ТочкаМаршрута(\"free\")\nПроцедура Все(Ответ) Экспорт\n\tОтвет.ТелоТекст = \"Все подрят\";\nКонецПроцедуры\n\n// Точка доступная ролям администраторы, пользователи.\n\u0026Роли(\"Администраторы, Пользователи\")\n\u0026ТочкаМаршрута(\"usradm\")\nПроцедура АдминыИПользователи(Ответ) Экспорт\n\tОтвет.ТелоТекст = \"Админы и пользователи\";\nКонецПроцедуры\n```\n\n# Работа с протоколом WebSocket\n\nВ winow в экспериментальном виде реализована поддержка веб-сокетов. Спека не полная, поддерживаются пока только текстовые сообщения. Разберем работу протокола на примере онлайн чата. Я не буду углубляться в часть фронта. Вот [пример](example/hwapp/view/chat.html) реализации на фронте. Основная суть такая - когда мы заходим на контрол  ```/chat``` осуществляется проверка, залогинился пользователь или нет. Если нет, переадресуем на страницу ввода логина.\n\nПосле ввода логина мы попадаем на страницу с чатом, где осуществляется подключение вебсокета, и начинается обмен сообщениями. \n\nА теперь разберем пример того, что происходит на стороне сервера. Обработкой входящих сообщений занимается такой же контроллер с точками маршрута, к которым мы привыкли. Каждая точка маршрута является \"топиком\" в рамках которого общается одно соединение веб сокета. В примере ниже у нас контроллер по адресу ```chat``` и точкой маршрута ```message```. У нас одно соединение, которое обменивается сообщениями в топике ```/chat/message```. Точка маршрута по обработке сообщений может принимать несколько параметров:\n- Идентификатор - идентификатор сессии, в которой можно хранить разные данные.\n- Топик - имя топика, в рамках которого происходит общение\n- Сообщение - расшифрованное сообщение, которое пришло от клиента.\n\nДля того, чтобы отправлять сообщения, нужно получить желудь ```БрокерСообщенийВебСокетов```. Который умеет следующие действия с сообщениями:\n\n- ОтправитьСообщение(Топик, Сообщение, Идентификатор) - Отправляет определенному клиенту сообщение в указанный топик.\n- ОтправитьСообщениеВсем(Топик, Сообщение) - Отправляет сообщение всем клиентам, подключенным к указанному топику.\n- ОтправитьСообщениеСписку(Топик, Сообщение, СписокИдентификаторов) - Отправляет сообщение массиву клиентов, подписанных на указанный топик.\n- ОтправитьСообщениеВсемКроме(Топик, Сообщение, СписокИсключенийИдентификаторов) - Отправляет сообщение всем, клиентам подписанным на указанный топик, кроме массива, переданного как параметр.\n\nТак же контроллер может иметь методы, помеченные аннотациями ```\u0026ПриПодключенииВебСокета(\"/имя/топика\")``` и ```ПриОтключенииВебСокета(\"/имя/топика\")```. Которые будут вызваны, после соответствующих событий, и принимать ```Идентификатор``` клиента, с которым произошло событие.\n\nВот полный пример с комментариями.\n\n```\nВебСокетЧат.os\n```\n\n```bsl\n\u0026Пластилин Перем БрокерСообщенийВебСокетов; // Инжектим желудь, который управляет отправкой сообщений\n\nПерем КешИменПользователей;\n\n\u0026Контроллер(\"/chat\") // помечаем контроллер и инициализируем кеш.\nПроцедура ПриСозданииОбъекта()\n\tКешИменПользователей = Новый Соответствие();\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"message\") // Обработчик входящего сообщения\nПроцедура ВходящееСообщение(Идентификатор, Топик, Сообщение) Экспорт\n\n\t// клиент понимает два вида сообщений, которые он отправил сам, и которые отправлены другими клиентами. Они по-разному отображаются на фронте.\n\t// Тут мы получаем из кеша имя пользователя по идентификатору соединения и отправляем клиентам.\n\n\tИмяПользователя = КешИменПользователей.Получить(Идентификатор);\n\n\tСообщить(СтрШаблон(\"Получено сообщение %1 от %2\", Сообщение, ИмяПользователя));\n\n\tМассив = Новый Массив();\n\tМассив.Добавить(Идентификатор);\n\n\tТекстПолучения = ФорматированноеСообщение(ИмяПользователя, Сообщение, Истина);\n\tТекстОтправки = ФорматированноеСообщение(ИмяПользователя, Сообщение, Ложь);\n\t\n\tБрокерСообщенийВебСокетов.ОтправитьСообщениеВсемКроме(Топик, ТекстПолучения, Массив);\n\tБрокерСообщенийВебСокетов.ОтправитьСообщениеСписку(Топик, ТекстОтправки, Массив);\n\nКонецПроцедуры\n\n\u0026Отображение(\"./hwapp/view/chat.html\")\n\u0026ТочкаМаршрута(\"\") // точка маршрута, которая отдаем страницу клиента.\nПроцедура Главная(Сессия, Ответ) Экспорт\n\t// если пользователь не закеширован, перенаправляем на страницу логина.\n\tИмя = КешИменПользователей.Получить(Сессия.Идентификатор());\n\tЕсли Имя = Неопределено Тогда\n\t\tОтвет.Перенаправить(\"/chat/login\");\n\tКонецЕсли;\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"login\") // страница ввода логина.\n\u0026Отображение(\"./hwapp/view/chatlogin.html\")\nПроцедура Логин() Экспорт\nКонецПроцедуры\n\n\u0026ТочкаМаршрута(\"loginprocess\") // обработка введенного логина, с кешированием имени.\nПроцедура ОбработкаЛогина(ИмяПользователя, Сессия, Ответ) Экспорт\n\tЕсли НЕ ЗначениеЗаполнено(ИмяПользователя) Тогда\n\t\tГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(ТекущаяУниверсальнаяДатаВМиллисекундах());\n\t\tИмяПользователя = \"Noname\" + Строка(ГенераторСлучайныхЧисел.СлучайноеЧисло(1, 999));\n\tКонецЕсли;\n\tСообщить(СтрШаблон(\"Регистрация пользователя %1\", ИмяПользователя));\n\tКешИменПользователей.Вставить(Сессия.Идентификатор(), ИмяПользователя);\n\tОтвет.Перенаправить(\"/chat\");\nКонецПроцедуры\n\n\u0026ПриПодключенииВебСокета(\"/chat/message\") // подписка на подключение пользователя\nПроцедура ПриПодключенииПользователя(Идентификатор) Экспорт\n\t// сообщим всем, что зашел новый пользователь.\n\tИмяПользователя = КешИменПользователей.Получить(Идентификатор);\n\n\tСообщить(СтрШаблон(\"Подключился %1\", ИмяПользователя));\n\n\tТекстСообщенияГостю = ФорматированноеСообщение(\"Оракул\", \"Привет \" + ИмяПользователя + \" !\", Истина);\n\t\n\tБрокерСообщенийВебСокетов.ОтправитьСообщениеВсем(\"/chat/message\", ТекстСообщенияГостю);\nКонецПроцедуры\n\n\u0026ПриОтключенииВебСокета(\"/chat/message\") // подписка на отключение пользователя.\nПроцедура ПриОтключенииПользователя(Идентификатор) Экспорт\n\t// сообщим всем, что пользователь вышел.\n\tИмяПользователя = КешИменПользователей.Получить(Идентификатор);\n\n\tСообщить(СтрШаблон(\"Отключился %1\", ИмяПользователя));\n\n\tТекстСообщения = ФорматированноеСообщение(\"Оракул\", ИмяПользователя + \" покинул чат\", Истина);\n\n\tБрокерСообщенийВебСокетов.ОтправитьСообщениеВсем(\"/chat/message\", ТекстСообщения);\nКонецПроцедуры\n\nФункция ФорматированноеСообщение(Автор, Текст, Получен)\n\tОбъектДляПарсинга = Новый Структура(\"Author, Time, Text, rcv\", Автор, Формат(ТекущаяДата(), \"ДФ=ЧЧ:мм\"), Текст, Получен);\n\n\tЗапись = новый ЗаписьJSON;\n\tЗапись.УстановитьСтроку();\n\tЗаписатьJSON(Запись, ОбъектДляПарсинга);\n\n\tВозврат Запись.Закрыть();\nКонецФункции\n```\n\nВот результат наших трудов.\n\n![ws](docs/ws-chat.gif)\n\n# Работа с механизмом server-sent events.\n\nПодробно можно прочитать на [вики](https://ru.wikipedia.org/wiki/Server-sent_events).\n\nДля реализации работы с механизмом нужно выполнить несколько шагов.\n\n1. Зарегистрировать топик, в конструкторе контроллера.\n2. Добавить обработчики событий, которые будут вызываться при подключении и отключении клиента. (опционально)\n3. Посылать сообщения клиенту в топики, при необходимости в соответствии с логикой приложения.\n\nРассмотрим на примере:\n\n```bsl\n\n// Подключаем необходимые зависимости\n\u0026Пластилин Перем БрокерСообщенийСобытийСервера;\n\u0026пластилин Перем ФабрикаОтветов;\n\n\u0026Контроллер(\"/sse\")\n\u0026Отображение(Шаблон = \"./hwapp/view/main_sse.html\")\nПроцедура ПриСозданииОбъекта(\u0026Пластилин ТопикиСерверныхСобытий)\n\n\tИмяТопика = \"/sse/acorndiscussion\";\n\n\t// регистрируем топик и обработчики открытия и закрытия\n\tТопикиСерверныхСобытий.Добавить(ИмяТопика,\n\t\t\t\t\t\t\t\t\tНовый Действие(ЭтотОбъект, \"НовоеПодключениеССЕ\"),\n\t\t\t\t\t\t\t\t\tНовый Действие(ЭтотОбъект, \"ОтключениеССЕ\"));\n\nКонецПроцедуры\n\nПроцедура НовоеПодключениеССЕ(Сессия, ИД) Экспорт\n\t\n\t// Код обработчика открытия соединения\n\nКонецПроцедуры\n\nПроцедура ОтключениеССЕ(Сессия, ИД) Экспорт\n\t\n\t// Код обработчика закрытия соединения\n\nКонецПроцедуры\n\nПроцедура ОтправитьСообщение()\n\t// Создаем сообщение\n\tСообщение = ФабрикаОтветов.СерверноеСобытие();\n\tСообщение.ТипСобытия(\"like\");\n\tСообщение.ДобавитьСтроку(\"Некий текст\");\n\n\t// Отправим сообщение всем клиентам, слушающим топик.\t\n\tБрокерСообщенийСобытийСервера.ОтправитьСообщениеВсем(ИмяТопика, Сообщение);\nКонецПроцедуры\n\n```\n\nПример клиента который подписывается на топик, и вызывает события в зависимости от типа полученного сообщения :\n\n```js\neventSource = new EventSource('sse/acorndiscussion');\n\neventSource.addEventListener('like', function(e) {\nlikeElem.innerHTML = e.data;\n});\n\neventSource.addEventListener('watch', function(e) {\nwatchElem.innerHTML = e.data;\n});\n\neventSource.addEventListener('newComment', function(e) {\naddComment(e.data);\n});\n\n```\n\nПолный пример можно посмотреть примерах - [контрол](example/hwapp/СерверныеСобытия.os) и [клиент](example/hwapp/view/main_sse.html).\n\nАпи объектов:\n\n```БрокерСообщенийВебСокетов```\n\n- ОтправитьСообщениеВсем(Топик, Сообщение): Отправка сообщения всем;\n- ОтправитьСообщениеПоИдСоединения(ИдСоединения, Сообщение): Отправка сообщения конкретному клиенту;\n- ОтправитьСообщениеСписку(Топик, Сообщение, МассивИдентификаторов): Отправка сообщения массиву клиентов;\n- ОтправитьСообщениеВсемКроме(Топик, Сообщение, МассивИсключенийИдентификаторов): Отправка сообщения с исключающим массивом клиентов;\n\n```ТопикиСерверныхСобытий```\n\n- Существует(Топик): Проверка существования топика;\n- Добавить(Топик, ОбработчикОткрытия, ОбработчикЗакрытия): Добавление топика. С возможностью подписки на события открытия и закрытия соединения. Тут принимаются объекты Действие, которые должны иметь интрефейс: ```Процедура ИмяОбработчика(Сессия, ИД) Экспорт``` Где сессия - идентификатор сессии, и идентифкатор конкретного соединения.\n\n```Сообщение```. Получается из фабрики ответов. ```Сообщение = ФабрикаОтветов.СерверноеСобытие();```\n\n- ТипСобытия(ТипСобытия): Установка типа события;\n- ДобавитьСтроку(Строка): Добавление строки в сообщение;\n- Идентификатор(ид) : Установка идентификатора сообщения;\n\nПример реактивного интерфейса на server sent events\n\n![sse](docs/sse.gif)\n\n# Использование cli\n\nwinow предоставляет интерфейс командной строки. Запуск приложения становится еще проще. Для этого нужно установить пакет [winow-cli](https://github.com/autumn-library/winow-cli).\n\n# Контейнеризация\n\nПриложение на winow можно, конечно, запустить в контейнере.\n\nВот небольшой пример, как это сделать.\n\nНужно в один каталог положить: \n* Само приложение [app](docker/app/), которое содержит скрипт запуска, контролы, файлы и картинки, ```autumn-properties.json``` со всеми настройками и т.д.\n* [Dockerfile](/docker/Dockerfile) для того, чтобы собрать образ, и прокинуть в него все файлы. \n* Скрипт запуска сервера [docker-entrypoint.sh](docker/docker-entrypoint.sh).\n* И все это дело удобно собирать одним скриптом [start.sh](docker/start.sh).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fautumn-library%2Fwinow","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fautumn-library%2Fwinow","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fautumn-library%2Fwinow/lists"}