diff --git a/.gitignore b/.gitignore index 9868ef0..b703f2c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ -.vscode/* -coverage/* -out/* -oscript_modules/* -*.ospx \ No newline at end of file +# OneScript +Components/ +out/ +oscript_modules/ +*.ospx + +# VS Code +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 05e5351..a515fb0 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,20 @@ [![Release](https://img.shields.io/github/release/Stivo182/oscript-httpbin.svg)](https://github.com/Stivo182/oscript-httpbin/releases) [![Тестирование](https://github.com/stivo182/oscript-httpbin/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/stivo182/oscript-httpbin/actions/workflows/test.yml) [![Статус порога качества](https://sonar.openbsl.ru/api/project_badges/measure?project=httpbin&metric=alert_status&token=sqb_2f7c84743fd1b295085c25a1b96cc8d975cd4dc7)](https://sonar.openbsl.ru/dashboard?id=httpbin) -[![Покрытие](https://sonar.openbsl.ru/api/project_badges/measure?project=httpbin&metric=coverage&token=sqb_2f7c84743fd1b295085c25a1b96cc8d975cd4dc7)](https://sonar.openbsl.ru/dashboard?id=httpbin) -Cервис позволяющий локально тестировать HTTP клиент. Разработан на [OneScript](https://github.com/EvilBeaver/OneScript) + [WINOW](https://github.com/autumn-library/winow). Поддерживает бо́льшую часть эндпоинтов [httpbin.org](https://httpbin.org/). +Локальный сервис для тестирования и отладки HTTP-запросов, реализованный на [OneScript](https://github.com/EvilBeaver/OneScript) с использованием фреймворка [WINOW](https://github.com/autumn-library/winow). +Проект предоставляет функциональность, аналогичную [httpbin.org](https://httpbin.org/), поддерживая большинство оригинальных эндпоинтов. * 1\. [Установка](#installation) * 2\. [Использование](#usage) * 2.1\. [CLI приложение](#cli-app) - * 2.2\. [Тестирование через asserts и 1connector](#testing) + * 2.2\. [Тестирование с asserts и 1connector](#testing) * 2.3\. [Swagger UI](#swagger-ui) * 3\. [Совместимость](#compatibility) * 4\. [Программный интерфейс](#api) -* 5\. [Ограничения](#limitations) -* 6\. [Сравнение с httpbin.org](#comparison) +* 5\. [Пользовательские эндпоинты](#custom-endpoints) +* 6\. [Ограничения](#limitations) +* 7\. [Сравнение с httpbin.org](#comparison) ## Установка @@ -27,16 +28,26 @@ opm install httpbin ### CLI приложение -Запуск сервиса через команду **run**: `httpbin run` +Запустите сервис с помощью команды: -Опции команды:
-`-h`, `--host` - имя хоста / IP адрес сервиса
-`-p`, `--port` - порт сервиса +``` bash +httpbin run +``` + +#### Опции команды: + +| Опция | Описание | Значение по умолчанию | +| --- | --- | --- | +| `-h`, `--host` | Имя хоста или IP-адрес сервиса | `127.0.0.1` | +| `-p`, `--port` | TCP-порт сервиса | `3333` | +| `--routes-handlers` | Путь к файлу или каталогу кастомных контроллеров | | + +### Тестирование с [asserts](https://github.com/oscript-library/asserts) и [1connector](https://github.com/vbondarevsky/1connector) -### Тестирование через [asserts](https://github.com/oscript-library/asserts) и [1connector](https://github.com/vbondarevsky/1connector) -_test.os:_ ``` bsl +# test.os + #Использовать asserts #Использовать 1connector #Использовать httpbin @@ -56,74 +67,120 @@ _test.os:_ &Тест Процедура ТестДолжен_ПроверитьПараметрыЗапроса() Экспорт + ПараметрыЗапроса = Новый Структура(); ПараметрыЗапроса.Вставить("key", "value"); - Ответ = КоннекторHTTP.Get(HttpBin.URL() + "/get", ПараметрыЗапроса); + Ответ = КоннекторHTTP.Get(HttpBin.URL("/get"), ПараметрыЗапроса); Ожидаем.Что(Ответ.КодСостояния).Равно(200); Ожидаем.Что(Ответ.Заголовки["Content-Type"]).Содержит("application/json"); Ожидаем.Что(Ответ.Json()["args"]["key"]).Равно("value"); + КонецПроцедуры ``` ### Swagger UI -На стартовой странице сервиса (адрес по умолчанию: `http://127.0.0.1:3334`) доступна визуальная документация API, а также возможность отправки запросов и получения ответов. +На стартовой странице сервиса (по умолчанию: `http://127.0.0.1:3333`) доступна интерактивная документация API через **Swagger UI**, а также возможность отправки запросов и получения ответов. ## Совместимость - - - - - - - - - - - - - - - - - - - - - - - - - - -
WindowsLinuxMacOS
OneScript 1.9OneScript 2.0OneScript 1.9OneScript 2.0OneScript 1.9OneScript 2.0
+Сервис протестирован и поддерживается на следующих платформах и версиях OneScript: + +| ОС | OneScript 1.9 | OneScript 2.0 | +| --- | --- | --- | +| **Windows** | ✅ | ✅ | +| **Linux** | ✅ | ✅ | +| **MacOS** | ✅ | ✅ | ## Программный интерфейс ### Класс `HttpBin` -Сервис по умолчанию запускается по адресу `127.0.0.1:3334` в фоновом режиме и с ожиданием завершения запуска сервиса.
-Класс реализован с текучим интерфейсом. +Класс предназначен для управления локальным HTTP-сервисом. +Реализован с использованием текучего интерфейса. + +#### Синтаксис + +`Новый HttpBin(<Хост>, <Порт>)` + +#### Параметры + +<Хост> (необязательный)
+Тип: _Строка_.
+IP-адрес или имя хоста.
+По умолчанию: _127.0.0.1_ + +<Порт> (необязательный)
+Тип: _Число_.
+Номер TCP-порта.
+По умолчанию: _3333_ + +#### Методы | Метод | Описание | | --- | --- | -| `Запустить()` | Запускает сервис | -| `Остановить()` | Останавливает сервис | -| `URL()` | URL сервиса | -| `Хост()` | Хост сервиса | -| `Порт()` | Порт сервиса | -| `УстановитьХост(<Хост>)` | Устанавливает хост сервиса | -| `УстановитьПорт(<Порт>)` | Устанавливает порт сервиса | -| `ЗапускатьВФоне(<Флаг>)` | Запуск сервиса будет выполнен в фоновом режиме | -| `ОжидатьЗапуск(<Флаг>)` | Ожидать завершение запуска сервиса, запущенного в фоновом режиме | -| `УстановитьТаймаутОжидания(<Таймаут>)` | Устанавливает таймаут ожидания запуска сервиса, запущенного в фоновом режиме | +| `Запустить()` | Запускает сервис в синхронном режиме с ожиданием полной готовности. | +| `ЗапуститьАсинх()` | Запускает сервис в асинхронном режиме без ожидания готовности. | +| `Остановить()` | Останавливает сервис. | +| `ОжидатьЗавершения()` | Ожидает завершения работы сервиса, приостанавливая выполнение текущего потока. | +| `Отвечает()` | Проверяет доступность сервиса через HTTP-запрос. | +| `Активен()` | Проверяет, что процесс сервиса запущен и не завершен. | +| `Порт()` | Возвращает номер TCP-порта, на котором работает сервис. | +| `УстановитьПорт(<Порт>)` | Устанавливает TCP-порт для запуска сервиса. | +| `Хост()` | Возвращает имя хоста или IP-адрес сервиса. | +| `УстановитьХост(<Хост>)` | Устанавливает имя хоста или IP-адрес для запуска сервиса. | +| `URL(<АдресРесурса>)` | Формирует полный URL-адрес сервиса с опциональным путем к ресурсу. | +| `ТаймаутЗапуска()` | Возвращает текущее значение таймаута запуска сервиса. | +| `УстановитьТаймаутЗапуска(<Таймаут>)` | Устанавливает максимальное время ожидания запуска сервиса. Применяется при синхронном запуске. | +| `РасположениеКонтроллеров()` | Возвращает текущий путь к файлу или каталогу с кастомными контроллерами. | +| `УстановитьРасположениеКонтроллеров(<Расположение>)` | Устанавливает путь к файлу или каталогу с кастомными контроллерами, определяющими точки маршрута сервиса. | + +## Пользовательские эндпоинты + +Сервис поддерживает подключение пользовательских контроллеров для добавления собственных эндпоинтов. + +### Создание контроллера + +Контроллер реализуется в виде класса OneScript с аннотацией `&Контроллер`. +В нём определяются точки маршрута (эндпоинты) с помощью аннотации `&ТочкаМаршрута`. +Подробнее см. в документации [WINOW](https://github.com/autumn-library/winow). + +**Пример контроллера** + +``` bsl +&Контроллер("/order") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +&ТочкаМаршрута("add") +Процедура Главная(Ответ) Экспорт + // Бизнес-логика +КонецПроцедуры +``` + +### Подключение контроллеров + +**Через программный интерфейс** + +Для подключения кастомных контроллеров используйте метод `УстановитьРасположениеКонтроллеров()`, указав путь к файлу или каталогу с контроллерами: + +``` bsl +HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./path/to/routes") + .Запустить(); +``` + +**Через CLI** + +``` bash +httpbin run --routes-handlers './path/to/routes-handlers' +``` ## Ограничения -На данный момент нет поддержки https. +- **Отсутствие поддержки HTTPS**: Сервис работает только по протоколу HTTP. ## Сравнение с httpbin.org diff --git a/autumn-properties.json b/autumn-properties.json index 1fee530..6fee23e 100644 --- a/autumn-properties.json +++ b/autumn-properties.json @@ -4,10 +4,5 @@ "КаталогиСФайлами": { "/static": "./src/app/view/static" } - }, - "cli": { - "ИмяПриложения": "httpbin", - "ПолноеИмяПриложения": "Cервис тестирования HTTP клиента", - "ВерсияПриложения": "1.3.0" } } \ No newline at end of file diff --git a/lib.config b/lib.config deleted file mode 100644 index 41db56c..0000000 --- a/lib.config +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/packagedef b/packagedef index 2453ac7..d1f3f56 100644 --- a/packagedef +++ b/packagedef @@ -1,14 +1,14 @@ Описание.Имя("httpbin") - .Версия("1.3.0") + .Версия("2.0.0") .Автор("Dmitry Ivanov") .АдресАвтора("https://github.com/Stivo182") - .Описание("Сервис тестирования HTTP клиента") + .АдресРепозитория("https://github.com/Stivo182/oscript-httpbin") + .Описание("Сервис для тестирования и отладки HTTP-запросов") .ВерсияСреды("1.9.2") .ВключитьФайл("src") - .ВключитьФайл("tests") .ВключитьФайл("packagedef") - .ВключитьФайл("lib.config") .ВключитьФайл("autumn-properties.json") + .ВключитьФайл("appsettings.json") .ВключитьФайл("README.md") .ВключитьФайл("LICENSE") .ЗависитОт("autumn", "4.3.11") @@ -16,10 +16,13 @@ .ЗависитОт("winow", "0.11.0") .ЗависитОт("compressor", "1.0.2") .ЗависитОт("1connector", "2.3.3") + .ЗависитОт("packageinfo", "1.1.1") + .ЗависитОт("fs", "1.2.0") + .ЗависитОт("logos", "1.7.1") .РазработкаЗависитОт("1testrunner") .РазработкаЗависитОт("asserts") .РазработкаЗависитОт("coverage") .РазработкаЗависитОт("1commands") - .РазработкаЗависитОт("fs") .РазработкаЗависитОт("jason") + .ОпределяетКласс("HttpBin", "src/core/Классы/HttpBin.os") .ИсполняемыйФайл("src/cmd/main.os", "httpbin") \ No newline at end of file diff --git "a/src/app/HttpBin_\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273.os" "b/src/app/\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" similarity index 74% rename from "src/app/HttpBin_\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273.os" rename to "src/app/\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" index 9238112..1bd1114 100644 --- "a/src/app/HttpBin_\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273.os" +++ "b/src/app/\320\236\321\201\320\275\320\276\320\262\320\275\320\276\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" @@ -1,8 +1,7 @@ -#Использовать "../internal" #Использовать 1connector -Перем Помощник; // см. ПомощникПодготовкиОтветов -Перем КаталогШаблонов; // Строка +Перем _Помощник; // ПомощникПодготовкиОтветов +Перем _КаталогШаблонов; // Строка #Область ТочкиМаршрута @@ -24,7 +23,7 @@ &GET Процедура ТочкаRobots(Ответ) Экспорт - Ответ.ТелоТекст = Помощник.ТекстRobots; + Ответ.ТелоТекст = _Помощник.ТекстRobots; КонецПроцедуры @@ -32,7 +31,7 @@ &GET Процедура ТочкаDeny(Ответ) Экспорт - Ответ.ТелоТекст = Помощник.ASCII_Deny; + Ответ.ТелоТекст = _Помощник.ASCII_Deny; Ответ.Заголовки.Удалить("Content-Type"); КонецПроцедуры @@ -41,8 +40,8 @@ &GET Процедура ТочкаIP(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + Данные = _Помощник.ПолучитьДанныеЗапроса("origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -52,7 +51,7 @@ Данные = Новый Структура("uuid", Строка(Новый УникальныйИдентификатор())); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -60,7 +59,7 @@ &GET Процедура ТочкаUuidN(Ответ, Знач Количество) Экспорт - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); Массив = Новый Массив(); Пока Количество > 0 Цикл @@ -70,7 +69,7 @@ Данные = Новый Структура("uuid", Массив); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -78,8 +77,8 @@ &GET Процедура ТочкаHeaders(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("headers", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + Данные = _Помощник.ПолучитьДанныеЗапроса("headers", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -87,12 +86,12 @@ &GET Процедура ТочкаUserAgent(Запрос, Ответ) Экспорт - UserAgent = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "User-Agent"); + UserAgent = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "User-Agent"); Результат = Новый Соответствие(); Результат.Вставить("user-agent", UserAgent); - Помощник.ЗаполнитьОтветJson(Ответ, Результат); + _Помощник.ЗаполнитьОтветJson(Ответ, Результат); КонецПроцедуры @@ -105,11 +104,11 @@ &ТочкаМаршрута("anything") Процедура ТочкаAnything(Запрос, ДанныеФормы, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("args, data, files, form, headers, json, method, origin, url", + Данные = _Помощник.ПолучитьДанныеЗапроса("args, data, files, form, headers, json, method, origin, url", Запрос, ДанныеФормы); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -150,14 +149,14 @@ &ТочкаМаршрута("redirect-to") Процедура ТочкаRedirectTo(Запрос, ДанныеФормы, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("args, form", Запрос, ДанныеФормы); + Данные = _Помощник.ПолучитьДанныеЗапроса("args, form", Запрос, ДанныеФормы); Если Запрос.Метод = "GET" Тогда URL = Данные["args"].Получить("url"); - КодСостояния = Помощник.ВЧисло(Данные["args"].Получить("status_code")); + КодСостояния = _Помощник.ВЧисло(Данные["args"].Получить("status_code")); Иначе URL = Данные["form"].Получить("url"); - КодСостояния = Помощник.ВЧисло(Данные["form"].Получить("status_code")); + КодСостояния = _Помощник.ВЧисло(Данные["form"].Получить("status_code")); КонецЕсли; Если КодСостояния >= КодыСостоянияHTTP.МножествоВыборов_300 @@ -192,8 +191,8 @@ Если Не СтрНайти(КодыСостояний, ",") Тогда - РазделеннаяСтрока = Помощник.РазделитьСтроку(КодыСостояний, ":"); - КодСостояния = Помощник.ВЧисло(РазделеннаяСтрока.Лево); + РазделеннаяСтрока = _Помощник.РазделитьСтроку(КодыСостояний, ":"); + КодСостояния = _Помощник.ВЧисло(РазделеннаяСтрока.Лево); Если КодСостояния = 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеверныйЗапрос_400); @@ -206,9 +205,9 @@ ВзвешенныйСписок = Новый Массив(); Для каждого Строка Из СтрРазделить(КодыСостояний, ",") Цикл - РазделеннаяСтрока = Помощник.РазделитьСтроку(Строка, ":"); + РазделеннаяСтрока = _Помощник.РазделитьСтроку(Строка, ":"); - КодСостояния = Помощник.ВЧисло(РазделеннаяСтрока.Лево); + КодСостояния = _Помощник.ВЧисло(РазделеннаяСтрока.Лево); Если КодСостояния = 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеверныйЗапрос_400); Ответ.ТелоТекст = "Invalid status code"; @@ -218,17 +217,17 @@ Если ПустаяСтрока(РазделеннаяСтрока.Право) Тогда Вес = 1; Иначе - Вес = Помощник.ВЧисло(РазделеннаяСтрока.Право); + Вес = _Помощник.ВЧисло(РазделеннаяСтрока.Право); КонецЕсли; ВзвешенныйСписок.Добавить(Новый Структура("Значение, Вес", КодСостояния, Вес)); КонецЦикла; - КодСостояния = Помощник.ВыбратьСлучайныйЭлементСУчетомВеса(ВзвешенныйСписок); + КодСостояния = _Помощник.ВыбратьСлучайныйЭлементСУчетомВеса(ВзвешенныйСписок); КонецЕсли; - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния); КонецПроцедуры @@ -240,10 +239,10 @@ Ответ.УстановитьТипКонтента("json"); Данные = Новый Соответствие(); - Помощник.ДополнитьСоответствие(Данные, Ответ.Заголовки); - Помощник.ДополнитьСоответствие(Данные, Запрос.ПараметрыИменные); + _Помощник.ДополнитьСоответствие(Данные, Ответ.Заголовки); + _Помощник.ДополнитьСоответствие(Данные, Запрос.ПараметрыИменные); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -257,7 +256,7 @@ КонецЦикла; Куки.Удалить("SessionID"); - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("cookies", Куки)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("cookies", Куки)); КонецПроцедуры @@ -309,14 +308,14 @@ &GET Процедура ТочкаBasicAuth(Запрос, Ответ, ИмяПользователя, Пароль) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "basic" И ДанныеАутентификации.ИмяПользователя = ИмяПользователя И ДанныеАутентификации.Пароль = Пароль Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); Иначе - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.НеАвторизован_401); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.НеАвторизован_401); КонецЕсли; КонецПроцедуры @@ -325,12 +324,12 @@ &GET Процедура ТочкаHiddenBasicAuth(Запрос, Ответ, ИмяПользователя, Пароль) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "basic" И ДанныеАутентификации.ИмяПользователя = ИмяПользователя И ДанныеАутентификации.Пароль = Пароль Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); Иначе Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеНайдено_404); КонецЕсли; @@ -341,10 +340,10 @@ &GET Процедура ТочкаBearer(Запрос, Ответ) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "bearer" Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, token", Истина, ДанныеАутентификации.Токен)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, token", Истина, ДанныеАутентификации.Токен)); Иначе Ответ.Заголовки["WWW-Authenticate"] = "Bearer"; Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеАвторизован_401); @@ -355,7 +354,7 @@ &ТочкаМаршрута("delay/{Секунд}") Процедура ТочкаDelay(Запрос, ДанныеФормы, Ответ, Секунд) Экспорт - Секунд = Помощник.ВЧисло(Секунд); + Секунд = _Помощник.ВЧисло(Секунд); Приостановить(Секунд * 1000); ПередатьПолныеДанныеВОтветJson(Запрос, ДанныеФормы, Ответ); @@ -378,11 +377,11 @@ &GET Процедура ТочкаCache(Запрос, Ответ) Экспорт - ЕстьЗаголовки = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-Modified-Since") <> Неопределено - Или Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-None-Match") <> Неопределено; + ЕстьЗаголовки = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-Modified-Since") <> Неопределено + Или _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-None-Match") <> Неопределено; Если Не ЕстьЗаголовки Тогда - Ответ.Заголовки["Last-Modified"] = Помощник.ДатаHTTP(); + Ответ.Заголовки["Last-Modified"] = _Помощник.ДатаВФорматеHTTP(ТекущаяУниверсальнаяДата()); Ответ.Заголовки["ETag"] = СтрЗаменить(Новый УникальныйИдентификатор(), "-", ""); ПередатьДанныеВОтветJsonGet(Запрос, Ответ); @@ -396,7 +395,7 @@ &GET Процедура ТочкаУстановитьCacheControl(Запрос, Ответ, Секунд) Экспорт - Секунд = Помощник.ВЧисло(Секунд); + Секунд = _Помощник.ВЧисло(Секунд); Ответ.Заголовки["Cache-Control"] = СтрШаблон("public, max-age=%1", Формат(Секунд, "ЧГ=")); @@ -408,11 +407,11 @@ &GET Процедура ТочкаETag(Запрос, Ответ, ETag) Экспорт - ЗаголовокIfNoneMatch = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-None-Match"); - ЗаголовокIfMatch = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-Match"); + ЗаголовокIfNoneMatch = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-None-Match"); + ЗаголовокIfMatch = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "If-Match"); - ЗначенияIfNoneMatch = Помощник.РаспаристьМногозначныйЗаголовок(ЗаголовокIfNoneMatch); - ЗначенияIfMatch = Помощник.РаспаристьМногозначныйЗаголовок(ЗаголовокIfMatch); + ЗначенияIfNoneMatch = _Помощник.РаспаристьМногозначныйЗаголовок(ЗаголовокIfNoneMatch); + ЗначенияIfMatch = _Помощник.РаспаристьМногозначныйЗаголовок(ЗаголовокIfMatch); Если ЗначениеЗаполнено(ЗначенияIfNoneMatch) Тогда Если ЗначенияIfNoneMatch.Найти(ETag) <> Неопределено Или ЗначенияIfNoneMatch.Найти("*") <> Неопределено Тогда @@ -444,11 +443,11 @@ &GET Процедура ТочкаBytes(ПараметрыЗапросаИменные, Ответ, КоличествоБайт) Экспорт - КоличествоБайт = Помощник.ВЧисло(КоличествоБайт); + КоличествоБайт = _Помощник.ВЧисло(КоличествоБайт); НачальноеЧисло = ПараметрыЗапросаИменные.Получить("seed"); Если Не НачальноеЧисло = Неопределено Тогда - НачальноеЧисло = Помощник.ВЧисло(НачальноеЧисло); + НачальноеЧисло = _Помощник.ВЧисло(НачальноеЧисло); ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(НачальноеЧисло); Иначе ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(); @@ -473,8 +472,8 @@ &GET Процедура ТочкаLinks(Ответ, Количество, Смещение) Экспорт - Количество = Помощник.ВЧисло(Количество); - Смещение = Помощник.ВЧисло(Смещение); + Количество = _Помощник.ВЧисло(Количество); + Смещение = _Помощник.ВЧисло(Смещение); Html = Новый Массив(); Html.Добавить("Links"); @@ -504,7 +503,7 @@ &GET Процедура ТочкаLinksRedirect(Ответ, Количество) Экспорт - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); URL = СтрШаблон("/links/%1/0", Формат(Количество, "ЧГ=")); Ответ.Перенаправить(URL); @@ -515,7 +514,7 @@ &GET Процедура ТочкаImage(Запрос, Ответ) Экспорт - Accept = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "Accept"); + Accept = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "Accept"); Если Accept = "image/webp" Тогда ТочкаImageWebp(Ответ); @@ -528,7 +527,7 @@ Или Accept = "*/*" Тогда ТочкаImagePng(Ответ); Иначе - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.Неприемлемо_406); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.Неприемлемо_406); КонецЕсли; КонецПроцедуры @@ -593,29 +592,29 @@ &ТочкаМаршрута("gzip") &GET Процедура ТочкаGZip(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("gzipped, headers, method, origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные, "gzip"); + Данные = _Помощник.ПолучитьДанныеЗапроса("gzipped, headers, method, origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные, "gzip"); КонецПроцедуры &ТочкаМаршрута("deflate") &GET Процедура ТочкаDeflate(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("deflated, headers, method, origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные, "deflate"); + Данные = _Помощник.ПолучитьДанныеЗапроса("deflated, headers, method, origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные, "deflate"); КонецПроцедуры &ТочкаМаршрута("brotli") &GET Процедура ТочкаBrotli(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("brotli, headers, method, origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные, "brotli"); + Данные = _Помощник.ПолучитьДанныеЗапроса("brotli, headers, method, origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные, "brotli"); КонецПроцедуры &ТочкаМаршрута("zstd") &GET Процедура ТочкаZStd(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("zstd, headers, method, origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные, "zstd"); + Данные = _Помощник.ПолучитьДанныеЗапроса("zstd, headers, method, origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные, "zstd"); КонецПроцедуры #КонецОбласти @@ -625,14 +624,14 @@ &Контроллер("/") Процедура ПриСозданииОбъекта(&Пластилин("ПомощникПодготовкиОтветов") ПомощникПодготовкиОтветов) - Помощник = ПомощникПодготовкиОтветов; - КаталогШаблонов = "./src/app/view"; + _Помощник = ПомощникПодготовкиОтветов; + _КаталогШаблонов = "./src/app/view"; КонецПроцедуры Процедура Redirect(Количество, ПередаватьАбсолютныйПуть, Ответ) - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); Если Количество <= 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеНайдено_404); @@ -648,7 +647,7 @@ КонецЕсли; Если ПередаватьАбсолютныйПуть Тогда - Адрес = Помощник.ПолучитьURL(Путь); + Адрес = _Помощник.URL(Путь); Иначе Адрес = Путь; КонецЕсли; @@ -659,25 +658,25 @@ Процедура ПередатьДанныеВОтветJsonGet(Запрос, Ответ) - Данные = Помощник.ПолучитьДанныеЗапроса("args, headers, origin, url", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + Данные = _Помощник.ПолучитьДанныеЗапроса("args, headers, origin, url", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры Процедура ПередатьПолныеДанныеВОтветJson(Запрос, ДанныеФормы, Ответ) - Данные = Помощник.ПолучитьДанныеЗапроса("args, data, files, form, headers, json, origin, url", + Данные = _Помощник.ПолучитьДанныеЗапроса("args, data, files, form, headers, json, origin, url", Запрос, ДанныеФормы); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры Процедура ОтдатьФайл(Путь, ТипКонтента, Ответ) Разделитель = ПолучитьРазделительПути(); - ПутьКФайлу = КаталогШаблонов + Разделитель + Путь; + ПутьКФайлу = _КаталогШаблонов + Разделитель + Путь; Ответ.Заголовки["Content-Type"] = ТипКонтента; Ответ.ТелоДвоичныеДанные = Новый ДвоичныеДанные(ПутьКФайлу); diff --git a/src/cmd/main.os b/src/cmd/main.os index 2f77450..7c5f6c9 100644 --- a/src/cmd/main.os +++ b/src/cmd/main.os @@ -1,14 +1,14 @@ #Использовать autumn #Использовать autumn-cli +#Использовать fs #Использовать "." -#Использовать "../core" +#Использовать "../internal" -Процедура УстановитьКорневойКаталог() - ТекущийКаталог = Новый Файл(ОбъединитьПути(ТекущийСценарий().Каталог, "../..")).ПолноеИмя; - УстановитьТекущийКаталог(ТекущийКаталог); -КонецПроцедуры +ПодключательКастомныхКонтроллеров = Новый ПодключательКастомныхКонтроллеров(); +ПодключательКастомныхКонтроллеров.НайтиИПодключить(); -УстановитьКорневойКаталог(); +ТекущийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../..")); +УстановитьТекущийКаталог(ТекущийКаталог); Поделка = Новый Поделка; Поделка.ЗапуститьПриложение(); \ No newline at end of file diff --git "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin_\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin_\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" deleted file mode 100644 index 4227acb..0000000 --- "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin_\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" +++ /dev/null @@ -1,27 +0,0 @@ -// BSLLS:UsingHardcodeNetworkAddress-off - -Перем HttpBin; // см. HttpBin - -&Опция(Имя = "h host", Описание = "Имя хоста / IP адрес сервиса") -&ТСтрока -&ПоУмолчанию("127.0.0.1") -Перем Хост; // Строка - -&Опция(Имя = "p port", Описание = "Порт сервиса") -&ТЧисло -&ПоУмолчанию(3334) -Перем Порт; // Число - -&КомандаПриложения(Имя = "run", Описание = "Запуск сервиса") -Процедура ПриСозданииОбъекта(&Пластилин("HttpBin") _HttpBin) - HttpBin = _HttpBin; -КонецПроцедуры - -&ВыполнениеКоманды -Процедура Запустить() Экспорт - HttpBin - .УстановитьХост(Хост) - .УстановитьПорт(Порт) - .ЗапускатьВФоне(Ложь) - .Запустить(); -КонецПроцедуры \ No newline at end of file diff --git "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" new file mode 100644 index 0000000..613ed0e --- /dev/null +++ "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\274\320\260\320\275\320\264\320\260\320\227\320\260\320\277\321\203\321\201\321\202\320\270\321\202\321\214.os" @@ -0,0 +1,65 @@ +// BSLLS:UsingHardcodeNetworkAddress-off + +#Область Опции + +&Опция( + Имя = "h host", + Описание = "Имя хоста или IP-адрес сервиса" +) +&ТСтрока +&ПоУмолчанию("127.0.0.1") +Перем _Хост; // Строка + +&Опция( + Имя = "p port", + Описание = "TCP-порт сервиса" +) +&ТЧисло +&ПоУмолчанию(3333) +Перем _Порт; // Число + +&Опция( + Имя = "parent-pid", + Описание = "PID родительского процесса" +) +&ТЧисло +&ПоУмолчанию(0) +Перем _ИдентификаторРодительскогоПроцесса; // Число + +&Опция( + Имя = "routes-handlers", + Описание = "Путь к файлу или каталогу кастомных контроллеров" +) +&ТМассивСтрок +Перем _КастомныеКонтроллеры; // BSLLS:UnusedLocalVariable-off + +#КонецОбласти + +#Область ОписаниеПеременных + +&Пластилин("КонтроллерРодительскогоПроцесса") +Перем _КонтроллерРодительскогоПроцесса; // КонтроллерРодительскогоПроцесса + +&Пластилин("ЗапускательСервиса") +Перем _ЗапускательСервиса; // ЗапускательСервиса + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +&КомандаПриложения(Имя = "run", Описание = "Запуск сервиса") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +&ВыполнениеКоманды +Процедура Запустить() Экспорт + + Если _ИдентификаторРодительскогоПроцесса > 0 Тогда + _КонтроллерРодительскогоПроцесса.НачатьНаблюдение(_ИдентификаторРодительскогоПроцесса); + КонецЕсли; + + _ЗапускательСервиса.Запустить(_Хост, _Порт); + +КонецПроцедуры + +#КонецОбласти \ No newline at end of file diff --git "a/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" new file mode 100644 index 0000000..4b0d1c3 --- /dev/null +++ "b/src/cmd/\320\232\320\273\320\260\321\201\321\201\321\213/\320\236\320\277\320\270\321\201\320\260\320\275\320\270\320\265\320\237\321\200\320\270\320\273\320\276\320\266\320\265\320\275\320\270\321\217.os" @@ -0,0 +1,22 @@ +#Использовать packageinfo + +Перем _ИнформацияОПакете; // ИнформацияОПакете - см. packageinfo + +&Желудь("ОписаниеКонсольногоПриложения") +&Верховный +Процедура ПриСозданииОбъекта() + ПолноеИмяФайла = ОбъединитьПути(ТекущийСценарий().Каталог, "../../..", "packagedef"); + _ИнформацияОПакете = Новый ИнформацияОПакете(ПолноеИмяФайла); +КонецПроцедуры + +Функция ИмяПриложения() Экспорт + Возврат _ИнформацияОПакете.Имя(); +КонецФункции + +Функция ПолноеИмяПриложения() Экспорт + Возврат _ИнформацияОПакете.Описание(); +КонецФункции + +Функция ВерсияПриложения() Экспорт + Возврат _ИнформацияОПакете.Версия(); +КонецФункции \ No newline at end of file diff --git "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin.os" "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin.os" index 706ff23..48f1f72 100644 --- "a/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin.os" +++ "b/src/core/\320\232\320\273\320\260\321\201\321\201\321\213/HttpBin.os" @@ -1,200 +1,311 @@ +// BSLLS:LatinAndCyrillicSymbolInWord-off // BSLLS:UsingHardcodeNetworkAddress-off -#Использовать autumn -#Использовать winow -#Использовать compressor #Использовать 1connector +#Использовать fs +#Использовать logos -Перем Поделка; // Ссылка на объект Поделка (autumn) -Перем ВебСервер; // Ссылка на объект ПрикладнойВебСервер (winow) -Перем НастройкиВебСервера; // Ссылка на объект Настройки (winow) -Перем ЗапускательВебПриложения; // Ссылка на объект ЗапускательВебПриложения (winow) - -Перем ЗапускатьВФоне; // Булево -Перем ОжидатьЗапуск; // Булево -Перем ТаймаутОжидания; // Количество +Перем _ТаймаутЗапуска; // Число - Таймаут запуска сервиса в секундах +Перем _ТаймаутПроверки; // Число - Таймаут HTTP-проверки доступности в секундах +Перем _ИмяХоста; // Строка - IP-адрес или доменное имя хоста +Перем _Порт; // Число - Номер TCP-порта сервиса +Перем _РасположениеКонтроллеров; // Строка, Неопределено - Путь к файлу или каталогу с кастомными контроллерами +Перем _Процесс; // Процесс, Неопределено - Объект запущенного процесса +Перем _Лог; // Лог - Логгер для отладочной информации #Область ПрограммныйИнтерфейс -// Запускает сервис +// Запускает HTTP-сервис в синхронном режиме с ожиданием полной готовности. +// Блокирует выполнение до тех пор, пока сервис не начнет отвечать на запросы +// или не истечет таймаут запуска. // // Возвращаемое значение: -// ЭтотОбъект +// ЭтотОбъект - Для возможности цепочки вызовов +// Функция Запустить() Экспорт + Возврат ЗапуститьПроцесс(Истина); +КонецФункции - Если ЗапускатьВФоне Тогда - ФоновыеЗадания.Выполнить(ЗапускательВебПриложения, "Запустить"); +// Запускает HTTP-сервис в асинхронном режиме без ожидания готовности. +// Возвращает управление немедленно после старта процесса. +// Используйте метод Отвечает() для проверки готовности сервиса. +// +// Возвращаемое значение: +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция ЗапуститьАсинх() Экспорт + Возврат ЗапуститьПроцесс(Ложь); +КонецФункции - Если ОжидатьЗапуск Тогда - НачатьОжиданиеЗапуска(); - КонецЕсли; - Иначе - ЗапускательВебПриложения.Запустить(); +// Останавливает работающий HTTP-сервис. +// Если сервис не запущен, метод ничего не делает. +// +// Возвращаемое значение: +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция Остановить() Экспорт + + Если Активен() Тогда + _Процесс.Завершить(); КонецЕсли; Возврат ЭтотОбъект; - + КонецФункции -// Останавливает сервис +// Ожидает завершения работы сервиса. +// Блокирует выполнение до тех пор, пока процесс сервиса не завершится. // // Возвращаемое значение: -// ЭтотОбъект -Функция Остановить() Экспорт - ВебСервер.Стоп(); +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция ОжидатьЗавершения() Экспорт + + Если Не Активен() Тогда + ВызватьИсключение "Невозможно ожидать завершения: процесс сервиса не запущен"; + КонецЕсли; + + _Процесс.ОжидатьЗавершения(); + Возврат ЭтотОбъект; + КонецФункции -// Возвращает адрес сервиса +// Проверяет доступность сервиса через HTTP-запрос. +// Выполняет HEAD-запрос к корневому URL сервиса и проверяет код ответа. // // Возвращаемое значение: -// Строка -Функция URL() Экспорт - Возврат СтрШаблон("http://%1:%2", НастройкиВебСервера.ИмяХоста, НастройкиВебСервера.Порт); +// Булево - Истина, если сервис доступен и возвращает HTTP 200 OK +// +Функция Отвечает() Экспорт + + Если Не Активен() Тогда + Возврат Ложь; + КонецЕсли; + + КодСостояния = ""; + + Попытка + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутПроверки)); + КодСостояния = Ответ.КодСостояния; + Исключение + _Лог.Отладка( + "Сервис недоступен по адресу %1. Ошибка: %2", + URL(), + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + Возврат Ложь; + КонецПопытки; + + Возврат КодСостояния = КодыСостоянияHTTP.ОК_200; + +КонецФункции + +// Проверяет, что процесс сервиса запущен и не завершен. +// +// Возвращаемое значение: +// Булево +// +Функция Активен() Экспорт + Возврат Не _Процесс = Неопределено И Не _Процесс.Завершен; КонецФункции -// Порт сервиса +// Возвращает номер TCP-порта, на котором работает сервис. // // Возвращаемое значение: -// Строка +// Число - Номер порта (1-65535) +// Функция Порт() Экспорт - Возврат НастройкиВебСервера.Порт; + Возврат _Порт; КонецФункции -// Устанавливает порт сервиса +// Устанавливает TCP-порт для запуска сервиса. +// Должен быть вызван до запуска сервиса. // // Параметры: -// Порт - Число - Номер порта +// Порт - Число - Номер порта (1-65535) // // Возвращаемое значение: -// ЭтотОбъект +// ЭтотОбъект - Для возможности цепочки вызовов +// Функция УстановитьПорт(Порт) Экспорт - НастройкиВебСервера.Порт = Порт; + _Порт = Порт; Возврат ЭтотОбъект; КонецФункции -// Имя хоста / ip адрес сервиса +// Возвращает имя хоста или IP-адрес сервиса. // // Возвращаемое значение: -// Строка +// Строка - Имя хоста или IP-адрес (например, "127.0.0.1" или "localhost") +// Функция Хост() Экспорт - Возврат НастройкиВебСервера.ИмяХоста; + Возврат _ИмяХоста; КонецФункции -// Устанавливает хост сервиса +// Устанавливает имя хоста или IP-адрес для запуска сервиса. +// Должен быть вызван до запуска сервиса. // // Параметры: -// Хост - Строка - Имя хоста / ip адрес +// Хост - Строка - Имя хоста или IP-адрес (например, "127.0.0.1", "localhost", "0.0.0.0") // // Возвращаемое значение: -// ЭтотОбъект +// ЭтотОбъект - Для возможности цепочки вызовов +// Функция УстановитьХост(Хост) Экспорт - НастройкиВебСервера.ИмяХоста = Хост; + _ИмяХоста = Хост; Возврат ЭтотОбъект; КонецФункции -// Запуск сервиса будет выполнен в фоновом режиме +// Формирует полный URL-адрес сервиса с опциональным путем к ресурсу. // // Параметры: -// Флаг - Булево +// АдресРесурса - Строка - Путь к ресурсу (например, "/get"). +// Слэш в начале необязателен. // // Возвращаемое значение: -// ЭтотОбъект -Функция ЗапускатьВФоне(Флаг = Истина) Экспорт - ЗапускатьВФоне = Флаг; - Возврат ЭтотОбъект; +// Строка - Полный URL (например, "http://127.0.0.1:3333/get") +// +Функция URL(Знач АдресРесурса = "") Экспорт + + Если Лев(АдресРесурса, 1) <> "/" Тогда + АдресРесурса = "/" + АдресРесурса; + КонецЕсли; + + Возврат СтрШаблон("http://%1:%2%3", _ИмяХоста, _Порт, АдресРесурса); + КонецФункции -// Ожидать завершение запуска сервиса, запущенного в фоновом режиме +// Устанавливает максимальное время ожидания запуска сервиса. +// Применяется при синхронном запуске через метод Запустить(). // // Параметры: -// Флаг - Булево +// Таймаут - Число - Таймаут в секундах // // Возвращаемое значение: -// ЭтотОбъект -Функция ОжидатьЗапуск(Флаг = Истина) Экспорт - ОжидатьЗапуск = Флаг; +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция УстановитьТаймаутЗапуска(Таймаут) Экспорт + _ТаймаутЗапуска = Таймаут; Возврат ЭтотОбъект; КонецФункции -// Устанавливает таймаут ожидания запуска сервиса, запущенного в фоновом режиме -// -// Параметры: -// Таймаут - Количество - Таймаут в секундах +// Возвращает текущее значение таймаута запуска сервиса. // // Возвращаемое значение: -// ЭтотОбъект -Функция УстановитьТаймаутОжидания(Таймаут) Экспорт - ТаймаутОжидания = Таймаут; - Возврат ЭтотОбъект; +// Число - Таймаут в секундах +// +Функция ТаймаутЗапуска() Экспорт + Возврат _ТаймаутЗапуска; КонецФункции -// Устанавливает задержку перед чтением сокета для прикладного веб-сервера (OneScript v1.9) +// Устанавливает путь к файлу или каталогу с кастомными контроллерами, определяющими точки маршрута сервиса. // // Параметры: -// Задержка - Число - Задержка в миллисекундах (По умолчанию 65 мс) +// Расположение - Строка - Путь к файлу или каталогу с контроллерами // // Возвращаемое значение: -// ЭтотОбъект -Функция УстановитьЗадержкуПередЧтениемСокета(Задержка) Экспорт - НастройкиВебСервера.ЗадержкаПередЧтениемСокета = Задержка; +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция УстановитьРасположениеКонтроллеров(Расположение) Экспорт + _РасположениеКонтроллеров = ФС.НормализоватьПуть(Расположение); Возврат ЭтотОбъект; КонецФункции +// Возвращает текущий путь к файлу или каталогу с кастомными контроллерами. +// +// Возвращаемое значение: +// Строка, Неопределено +// +Функция РасположениеКонтроллеров() Экспорт + Возврат _РасположениеКонтроллеров; +КонецФункции + #КонецОбласти #Область СлужебныеПроцедурыИФункции -// Cервис тестирования HTTP клиента. +// Конструктор класса. // -// Сервис по умолчанию запускается по адресу 127.0.0.1:3334 в фоновом режиме -// и с ожиданием завершения запуска сервиса. +// По умолчанию сервис настроен на локальный адрес 127.0.0.1:3333 +// с таймаутом запуска 5 секунд. // // Параметры: -// ПоделкаОсени - Объект - Ссылка на объект Поделка (autumn) -&Желудь -Процедура ПриСозданииОбъекта(&Пластилин("Поделка") ПоделкаОсени = Неопределено) - - ХостПоУмолчанию = "127.0.0.1"; - ПортПоУмолчанию = 3334; - ТаймаутОжиданияПоУмолчанию = 5; - ЗадержкаПередЧтениемСокета = 65; - - Если ПоделкаОсени = Неопределено Тогда - УстановитьКорневойКаталог(); - - Поделка = Новый Поделка; - Поделка.ЗапуститьПриложение(); - Иначе - Поделка = ПоделкаОсени; - КонецЕсли; +// Хост - Строка - IP-адрес или имя хоста (по умолчанию "127.0.0.1") +// Порт - Число - Номер TCP-порта (по умолчанию 3333) +// +Процедура ПриСозданииОбъекта(Хост = "127.0.0.1", Порт = 3333) // BSLLS:MagicNumber-off - ВебСервер = Поделка.НайтиЖелудь("ВебСервер"); - НастройкиВебСервера = Поделка.НайтиЖелудь("Настройки"); - ЗапускательВебПриложения = Поделка.НайтиЖелудь("ЗапускательВебПриложения"); + ТаймаутЗапускаПоУмолчанию = 5; + _ТаймаутПроверки = 3; - НастройкиВебСервера.РазмерБуфера = 0; + УстановитьХост(Хост); + УстановитьПорт(Порт); + УстановитьТаймаутЗапуска(ТаймаутЗапускаПоУмолчанию); - УстановитьХост(ХостПоУмолчанию); - УстановитьПорт(ПортПоУмолчанию); - ЗапускатьВФоне(); - ОжидатьЗапуск(); - УстановитьТаймаутОжидания(ТаймаутОжиданияПоУмолчанию); - УстановитьЗадержкуПередЧтениемСокета(ЗадержкаПередЧтениемСокета); + _Лог = Логирование.ПолучитьЛог("oscript.lib.httpbin"); КонецПроцедуры +Функция ЗапуститьПроцесс(Синхронно) + + Если Активен() Тогда + ВызватьИсключение "Невозможно запустить сервис: процесс уже запущен."; + КонецЕсли; + + РабочийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")); + ИсполняемыйФайл = НайтиИсполняемыйФайл(); + + СтрокаКоманды = Новый Массив(); + СтрокаКоманды.Добавить(ОбернутьВДвойныеКавычки(ИсполняемыйФайл)); + СтрокаКоманды.Добавить(ОбернутьВКавычки(ТочкаВходаКонсольногоПриложения())); + СтрокаКоманды.Добавить("run"); + СтрокаКоманды.Добавить("--host"); + СтрокаКоманды.Добавить(_ИмяХоста); + СтрокаКоманды.Добавить("--port"); + СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); + СтрокаКоманды.Добавить("--parent-pid"); + СтрокаКоманды.Добавить(Формат(ТекущийПроцесс().Идентификатор, "ЧГ=")); + + Если ЗначениеЗаполнено(_РасположениеКонтроллеров) Тогда + СтрокаКоманды.Добавить("--routes-handlers"); + СтрокаКоманды.Добавить(ОбернутьВКавычки(_РасположениеКонтроллеров)); + КонецЕсли; + + СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); + + _Лог.Отладка("Запуск процесса: %1", СтрокаКоманды); + + _Процесс = СоздатьПроцесс(СтрокаКоманды, РабочийКаталог, , , , ПеременныеСреды()); + _Процесс.Запустить(); + + Если Синхронно Тогда + НачатьОжиданиеЗапуска(); + + Если Не Активен() Тогда + ВызватьИсключение СтрШаблон("Не удалось запустить сервис по адресу %1:%2", Хост(), Порт()); + КонецЕсли; + КонецЕсли; + + Возврат ЭтотОбъект; + +КонецФункции + Процедура НачатьОжиданиеЗапуска() ЗадержкаМеждуПопытками = 100; // Миллисекунд ВремяНачала = ТекущаяУниверсальнаяДата(); + НомерПопытки = 0; Пока Истина Цикл + НомерПопытки = НомерПопытки + 1; КодСостояния = Неопределено; ТекстИсключения = ""; + + _Лог.Отладка("Проверка готовности сервиса %1. Попытка: %2", URL(), НомерПопытки); + Попытка - Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", ТаймаутОжидания)); + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутПроверки)); КодСостояния = Ответ.КодСостояния; Исключение ТекстИсключения = СокрП(КраткоеПредставлениеОшибки(ИнформацияОбОшибке())); @@ -202,24 +313,32 @@ КонецПопытки; Если КодСостояния = КодыСостоянияHTTP.ОК_200 Тогда + _Лог.Отладка("Сервис %1 успешно запущен и готов к работе.", URL()); Прервать; КонецЕсли; ПрошлоСекунд = ТекущаяУниверсальнаяДата() - ВремяНачала; - Если ПрошлоСекунд > ТаймаутОжидания Или ЗначениеЗаполнено(КодСостояния) Тогда + Если ПрошлоСекунд > _ТаймаутЗапуска Или ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон( - "Не удалось запустить веб-сервер по адресу %1:%2 в течение %3 сек.", - НастройкиВебСервера.ИмяХоста, - НастройкиВебСервера.Порт, - ТаймаутОжидания); + "Превышен таймаут запуска сервиса %1:%2 (%3 сек). " + + "Сервис не начал отвечать на запросы в течение отведенного времени.", + _ИмяХоста, + _Порт, + _ТаймаутЗапуска); Если ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон("%1: - |%2", ТекстОшибки, КодыСостоянияHTTP.Представление(КодСостояния)); + |%2", + ТекстОшибки, + КодыСостоянияHTTP.Представление(КодСостояния) + ); Иначе ТекстОшибки = СтрШаблон("%1: - |%2", ТекстОшибки, ТекстИсключения); + |%2", + ТекстОшибки, + ТекстИсключения + ); КонецЕсли; ВызватьИсключение ТекстОшибки; @@ -232,9 +351,46 @@ КонецПроцедуры -Процедура УстановитьКорневойКаталог() - ТекущийКаталог = Новый Файл(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")).ПолноеИмя; - УстановитьТекущийКаталог(ТекущийКаталог); -КонецПроцедуры +Функция ТочкаВходаКонсольногоПриложения() + Возврат ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../cmd/main.os")); +КонецФункции + +Функция НайтиИсполняемыйФайл() + + КаталогПрограммы = КаталогПрограммы(); + + ВариантыИмени = Новый Массив(); + ВариантыИмени.Добавить("oscript.exe"); + ВариантыИмени.Добавить("oscript"); + + Для Каждого ИмяФайла Из ВариантыИмени Цикл + ИсполняемыйФайл = ОбъединитьПути(КаталогПрограммы, ИмяФайла); + Если ФС.ФайлСуществует(ИсполняемыйФайл) Тогда + Возврат ИсполняемыйФайл; + КонецЕсли; + КонецЦикла; + + ВызватьИсключение СтрШаблон("Не найден исполняемый файл oscript в каталоге: %1", КаталогПрограммы); + +КонецФункции + +Функция ОбернутьВДвойныеКавычки(Строка) + Возврат СтрШаблон("%2%1%2", Строка, """"); +КонецФункции + +Функция ОбернутьВКавычки(Строка) + Кавычка = ?(ЭтоWindows(), """", "'"); + Возврат СтрШаблон("%2%1%2", Строка, Кавычка); +КонецФункции + +Функция ЭтоWindows() + + СистемнаяИнформация = Новый СистемнаяИнформация(); + ТекущаяПлатформа = СистемнаяИнформация.ТипПлатформы; + + Возврат ТекущаяПлатформа = ТипПлатформы.Windows_x86_64 + Или ТекущаяПлатформа = ТипПлатформы.Windows_x86; + +КонецФункции #КонецОбласти \ No newline at end of file diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\241\320\265\321\200\320\262\320\270\321\201\320\260.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\241\320\265\321\200\320\262\320\270\321\201\320\260.os" new file mode 100644 index 0000000..7732c4c --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\227\320\260\320\277\321\203\321\201\320\272\320\260\321\202\320\265\320\273\321\214\320\241\320\265\321\200\320\262\320\270\321\201\320\260.os" @@ -0,0 +1,43 @@ + +#Использовать winow + +Перем _Хост; // Строка +Перем _Порт; // Число + +&Пластилин("Настройки") +Перем _НастройкиВебСервера; // Настройки - см. winow + +&Пластилин("ЗапускательВебПриложения") +Перем _ЗапускательВебПриложения; // ЗапускательВебПриложения - см. winow + +&Желудь +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +// Запускает сервис через winow +// +// Параметры: +// Хост - Строка - IP-адрес или имя хоста +// Порт - Число - Номер TCP-порта +Процедура Запустить(Хост, Порт) Экспорт + + _Хост = Хост; + _Порт = Порт; + + НастроитьВебСервер(); + + _ЗапускательВебПриложения.Запустить(); + +КонецПроцедуры + +Процедура НастроитьВебСервер() + + _НастройкиВебСервера.РазмерБуфера = 0; + _НастройкиВебСервера.ИмяХоста = _Хост; + _НастройкиВебСервера.Порт = _Порт; + + Если ПолучитьПеременнуюСреды("HTTPBIN_IS_TEST_MODE") = "true" Тогда + _НастройкиВебСервера.ЗадержкаПередЧтениемСокета = 400; + КонецЕсли; + +КонецПроцедуры \ No newline at end of file diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\240\320\276\320\264\320\270\321\202\320\265\320\273\321\214\321\201\320\272\320\276\320\263\320\276\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\260.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\240\320\276\320\264\320\270\321\202\320\265\320\273\321\214\321\201\320\272\320\276\320\263\320\276\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\260.os" new file mode 100644 index 0000000..3799356 --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\240\320\276\320\264\320\270\321\202\320\265\320\273\321\214\321\201\320\272\320\276\320\263\320\276\320\237\321\200\320\276\321\206\320\265\321\201\321\201\320\260.os" @@ -0,0 +1,60 @@ +#Использовать logos + +Перем _ОжиданиеМеждуПроверками; // Число - Миллисекунды между проверками +Перем _ФоновоеЗадание; // ФоновоеЗадание, Неопределено +Перем _Лог; // Лог + +&Желудь +Процедура ПриСозданииОбъекта() + _ОжиданиеМеждуПроверками = 5000; + _Лог = Логирование.ПолучитьЛог("oscript.lib.httpbin"); +КонецПроцедуры + +#Область ПрограммныйИнтерфейс + +// Запускает фоновое наблюдение за процессом с указанным идентификатором. +// Если наблюдаемый процесс завершается, текущий процесс автоматически завершится. +// +// Параметры: +// PID - Число - Идентификатор родительского процесса для наблюдения +// +Процедура НачатьНаблюдение(PID) Экспорт + + Если Не _ФоновоеЗадание = Неопределено + И _ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно Тогда + ВызватьИсключение "Невозможно запустить наблюдение: уже активно наблюдение за процессом."; + КонецЕсли; + + _Лог.Отладка("Запуск наблюдения за родительским процессом PID=%1", PID); + + ПараметрыМетода = Новый Массив(); + ПараметрыМетода.Добавить(PID); + + _ФоновоеЗадание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПериодическаяПроверкаПроцесса", ПараметрыМетода); + +КонецПроцедуры + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура ПериодическаяПроверкаПроцесса(PID) Экспорт + + Пока Истина Цикл + ЗавершитьЕслиПроцессОтсутствует(PID); + Приостановить(_ОжиданиеМеждуПроверками); + КонецЦикла; + +КонецПроцедуры + +Процедура ЗавершитьЕслиПроцессОтсутствует(PID) + + Процесс = НайтиПроцессПоИдентификатору(PID); + Если Процесс = Неопределено Тогда + _Лог.Отладка("Родительский процесс PID=%1 завершён. Завершение текущего процесса", PID); + ТекущийПроцесс().Завершить(); + КонецЕсли; + +КонецПроцедуры + +#КонецОбласти \ No newline at end of file diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\260\321\202\320\265\320\273\321\214\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\321\205\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\276\320\262.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\260\321\202\320\265\320\273\321\214\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\321\205\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\276\320\262.os" new file mode 100644 index 0000000..dc9807b --- /dev/null +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\264\320\272\320\273\321\216\321\207\320\260\321\202\320\265\320\273\321\214\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\321\205\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\320\276\320\262.os" @@ -0,0 +1,85 @@ +#Использовать fs + +#Область ПрограммныйИнтерфейс + +// Находит и подключает кастомные контроллеры из путей, указанных в аргументах командной строки. +// +Процедура НайтиИПодключить() Экспорт + + МассивФайлов = НайтиФайлыИзПереданныхАргументов(); + + Для Каждого ПолноеИмя Из МассивФайлов Цикл + + Путь = ФС.НормализоватьПуть(ПолноеИмя); + + Если ФС.КаталогСуществует(Путь) Тогда + ПодключитьИзКаталога(Путь); + ИначеЕсли ФС.ФайлСуществует(Путь) Тогда + Подключить(Путь); + Иначе + ВызватьИсключение СтрШаблон("Не удалось подключить контроллер '%1': файл или каталог не существует", ПолноеИмя); + КонецЕсли; + + КонецЦикла; + +КонецПроцедуры + +// Подключает контроллер из указанного файла. +// +// Параметры: +// ПолноеИмя - Строка - Полный путь к файлу контроллера (.os) +// +Процедура Подключить(ПолноеИмя) Экспорт + + Имя = Новый Файл(ПолноеИмя).ИмяБезРасширения; + + Попытка + ПодключитьСценарий(ПолноеИмя, Имя); + Исключение + ВызватьИсключение СтрШаблон( + "Не удалось подключить контроллер '%1': %2", + ПолноеИмя, + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + КонецПопытки; + +КонецПроцедуры + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура ПодключитьИзКаталога(Каталог) + + Файлы = НайтиФайлы(Каталог, "*.os"); + + Для Каждого Файл Из Файлы Цикл + Подключить(Файл.ПолноеИмя); + КонецЦикла; + +КонецПроцедуры + +Функция НайтиФайлыИзПереданныхАргументов() + + Файлы = Новый Массив(); + + НайденКлюч = Ложь; + Для Каждого Значение Из АргументыКоманднойСтроки Цикл + + Если НайденКлюч Тогда + Файлы.Добавить(Значение); + НайденКлюч = Ложь; + Продолжить; + КонецЕсли; + + Если Значение = "--routes-handlers" Тогда + НайденКлюч = Истина; + КонецЕсли; + + КонецЦикла; + + Возврат Файлы; + +КонецФункции + +#КонецОбласти \ No newline at end of file diff --git "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\274\320\276\321\211\320\275\320\270\320\272\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\270\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os" "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\274\320\276\321\211\320\275\320\270\320\272\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\270\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os" index 8885699..71817a1 100644 --- "a/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\274\320\276\321\211\320\275\320\270\320\272\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\270\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os" +++ "b/src/internal/\320\232\320\273\320\260\321\201\321\201\321\213/\320\237\320\276\320\274\320\276\321\211\320\275\320\270\320\272\320\237\320\276\320\264\320\263\320\276\321\202\320\276\320\262\320\272\320\270\320\236\321\202\320\262\320\265\321\202\320\276\320\262.os" @@ -1,68 +1,72 @@ // BSLLS:ExportVariables-off #Использовать 1connector +#Использовать compressor -Перем ТекстRobots Экспорт; // Строка -Перем ASCII_Deny Экспорт; // Строка -Перем ASCII_Teapot; // Строка -Перем АдресРедиректа; // Строка -Перем ПоддерживаемыеМедиаТипы; // Массив из Строка +Перем ТекстRobots Экспорт; // Строка - Содержимое файла robots.txt +Перем ASCII_Deny Экспорт; // Строка - ASCII-арт для страницы отказа доступа +Перем ASCII_Teapot; // Строка - ASCII-арт чайника для HTTP 418 +Перем АдресРедиректа; // Строка - URL для редиректов (по умолчанию) +Перем ПоддерживаемыеМедиаТипы; // Массив из Строка - Список поддерживаемых MIME-типов -Перем НастройкиВебСервера; // Ссылка на объект Настройки (winow) -Перем Парсеры; // Ссылка на объект Парсеры (winow) -Перем ЭтоНативныйВебСервер; // Булево +Перем НастройкиВебСервера; // Настройки - Настройки веб-сервера (см. winow) +Перем Парсеры; // Парсеры - Утилиты парсинга (см. winow) +Перем ЭтоНативныйВебСервер; // Булево - Признак использования нативного веб-сервера #Область ПрограммныйИнтерфейс -// Заполняет ответ json данными +// Формирует JSON-ответ с опциональным сжатием. // // Параметры: -// Ответ - winow.Ответ - Ответ -// Данные - Соответствие, Структура - Данные для передачи в тело ответа -// АлгоритмСжатия - Строка - Алгоритм сжатия данных (gzip, deflate, brotli, zstd) +// Ответ - Ответ - Объект ответа для заполнения (см. winow) +// Данные - Соответствие, Структура - Данные для сериализации в JSON +// АлгоритмСжатия - Строка, Неопределено - Алгоритм сжатия тела ответа (gzip, deflate, brotli, zstd) +// Процедура ЗаполнитьОтветJson(Ответ, Данные, АлгоритмСжатия = Неопределено) Экспорт Ответ.УстановитьТипКонтента("json"); ТекстJson = ВJson(Данные); - + Компрессор = Неопределено; + Если АлгоритмСжатия = "gzip" Тогда Компрессор = Новый GZipКомпрессор(); - Ответ.Заголовки["Content-Encoding"] = "gzip"; - Ответ.ТелоДвоичныеДанные = Компрессор.Упаковать(ПолучитьДвоичныеДанныеИзСтроки(ТекстJson)); + ЗаголовокСжатия = "gzip"; ИначеЕсли АлгоритмСжатия = "deflate" Тогда Компрессор = Новый DeflateКомпрессор(); - Ответ.Заголовки["Content-Encoding"] = "deflate"; - Ответ.ТелоДвоичныеДанные = Компрессор.Упаковать(ПолучитьДвоичныеДанныеИзСтроки(ТекстJson)); + ЗаголовокСжатия = "deflate"; ИначеЕсли АлгоритмСжатия = "brotli" Тогда Компрессор = Новый BrotliКомпрессор(); - Ответ.Заголовки["Content-Encoding"] = "br"; - Ответ.ТелоДвоичныеДанные = Компрессор.Упаковать(ПолучитьДвоичныеДанныеИзСтроки(ТекстJson)); + ЗаголовокСжатия = "br"; ИначеЕсли АлгоритмСжатия = "zstd" Тогда Компрессор = Новый ZStdКомпрессор(); - Ответ.Заголовки["Content-Encoding"] = "zstd"; - Ответ.ТелоДвоичныеДанные = Компрессор.Упаковать(ПолучитьДвоичныеДанныеИзСтроки(ТекстJson)); + ЗаголовокСжатия = "zstd"; - Иначе + КонецЕсли; + Если Не Компрессор = Неопределено Тогда + ДвоичныеДанные = ПолучитьДвоичныеДанныеИзСтроки(ТекстJson); + Ответ.Заголовки["Content-Encoding"] = ЗаголовокСжатия; + Ответ.ТелоДвоичныеДанные = Компрессор.Упаковать(ДвоичныеДанные); + Иначе Ответ.ТелоТекст = ТекстJson; - КонецЕсли; КонецПроцедуры -// Заполняет ответ по состоянию +// Заполняет ответ стандартным содержимым для указанного HTTP-статуса. // // Параметры: -// Ответ - winow.Ответ - Ответ -// КодСостояния - Число - Код состояния HTTP +// Ответ - Ответ - Объект ответа для заполнения (см. winow) +// КодСостояния - Число - Код HTTP-статуса +// Процедура ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния) Экспорт Ответ.УстановитьСостояние(КодСостояния); @@ -105,15 +109,32 @@ КонецПроцедуры -// Данные входящего запроса +// Извлекает и структурирует данные из входящего HTTP-запроса. +// Возвращает только указанные ключи из полного набора данных запроса. // // Параметры: -// Ключи - Строка - Ключи через запятую -// Запрос - winow.ВходящийЗапрос - Входящий запрос -// ДанныеФормы - winow.ДанныеСоставнойФормы - Данные составной формы (multipart) +// Ключи - Строка - Имена возвращаемых полей через запятую. +// Доступные поля: url, method, args, headers, origin, json, +// data, form, files, gzipped, deflated, brotli, zstd +// Запрос - ВходящийЗапрос - Объект входящего HTTP-запроса (см. winow) +// ДанныеФормы - ДанныеСоставнойФормы, Неопределено - Данные multipart/form-data (см. winow) // // Возвращаемое значение: -// Структура +// Структура - Структура с запрошенными полями: +// * url - Строка - Полный URL запроса +// * method - Строка - HTTP-метод (GET, POST, PUT и т.д.) +// * args - Соответствие - Параметры строки запроса +// * headers - Соответствие - Заголовки запроса +// * origin - Строка - IP-адрес клиента +// * json - Соответствие, null - Распарсенное JSON-тело (если Content-Type: application/json) +// * data - Строка - Сырое тело запроса (если не form-data) +// * form - Соответствие - Данные формы (application/x-www-form-urlencoded или multipart) +// * files - Соответствие - Загруженные файлы из multipart/form-data +// * gzipped - Булево - Всегда Истина (поддержка gzip) +// * deflated - Булево - Всегда Истина (поддержка deflate) +// * brotli - Булево - Всегда Истина (поддержка brotli) +// * zstd - Булево - Всегда Истина (поддержка zstd) +// Функция ПолучитьДанныеЗапроса(Ключи, Запрос, ДанныеФормы = Неопределено) Экспорт ДанныеФормы = РазделитьДанныеФормы(ДанныеФормы); @@ -128,7 +149,7 @@ КонецЕсли; ДанныеЗапроса = Новый Структура(); - ДанныеЗапроса.Вставить("url", ПолучитьURL(Запрос.ПолныйПуть)); + ДанныеЗапроса.Вставить("url", URL(Запрос.ПолныйПуть)); ДанныеЗапроса.Вставить("method", Запрос.Метод); ДанныеЗапроса.Вставить("args", Запрос.ПараметрыИменные); ДанныеЗапроса.Вставить("headers", ЗаголовкиДляJson(Запрос.Заголовки)); @@ -148,20 +169,24 @@ КонецФункции -// Данные аутентификации из входящего запроса +// Извлекает данные аутентификации из заголовка Authorization. +// Поддерживает схемы Basic и Bearer. // // Параметры: -// Запрос - winow.ВходящийЗапрос - Входящий запрос +// Запрос - ВходящийЗапрос - Объект входящего HTTP-запроса (см. winow) // // Возвращаемое значение: -// Структура: -// Общее: -// * Тип - Строка - Тип аутентификации (basic, bearer) -// Basic: +// Структура - Данные аутентификации: +// Общие поля: +// * Тип - Строка - Схема аутентификации ("basic", "bearer" или пустая строка) +// +// Для Basic-аутентификации (Тип = "basic"): // * ИмяПользователя - Строка, Неопределено - Имя пользователя // * Пароль - Строка, Неопределено - Пароль -// Bearer: -// * Токен - Строка - Токен +// +// Для Bearer-токена (Тип = "bearer"): +// * Токен - Строка - Значение токена +// Функция ДанныеАутентификации(Запрос) Экспорт Данные = Новый Структура("Тип", ""); @@ -204,14 +229,15 @@ КонецФункции -// Значение заголовка по имени +// Извлекает значение HTTP-заголовка по имени (регистронезависимый поиск). // // Параметры: // Заголовки - Соответствие, СловарьЗаголовков - Коллекция заголовков -// Имя - Строка - Имя заголовка +// Имя - Строка - Имя заголовка для поиска (регистр не важен) // // Возвращаемое значение: -// Строка, Неопределено +// Строка, Соответствие, Неопределено +// Функция ЗначениеЗаголовка(Заголовки, Знач Имя) Экспорт Имя = НРег(Имя); @@ -231,13 +257,15 @@ КонецФункции -// Парсит многозначный заголовок +// Парсит многозначный HTTP-заголовок (например, If-Match, If-None-Match). +// Обрабатывает значения с кавычками и опциональным префиксом W/ (weak ETag). // // Параметры: -// Значение - Строка - Значение заголовка +// Значение - Строка - Значение заголовка с несколькими значениями через запятую // // Возвращаемое значение: -// Массив из Строка - Массив значений +// Массив из Строка - Массив извлечённых значений +// Функция РаспаристьМногозначныйЗаголовок(Значение) Экспорт Результат = Новый Массив(); @@ -260,56 +288,66 @@ КонецФункции -// Получает URL к запущенному веб-серверу с добавлением пути +// Формирует полный URL-адрес сервиса с опциональным путем к ресурсу. // // Параметры: -// ПолныйПуть - Строка - Путь после порта веб-сервера, включая "/" +// АдресРесурса - Строка - Путь к ресурсу (например, "/get"). +// Слэш в начале необязателен. // // Возвращаемое значение: -// Строка - URL -Функция ПолучитьURL(ПолныйПуть) Экспорт +// Строка - Полный URL +// +Функция URL(Знач АдресРесурса = "") Экспорт + + СтандартныйПорт = 80; + + Если Лев(АдресРесурса, 1) <> "/" Тогда + АдресРесурса = "/" + АдресРесурса; + КонецЕсли; - Возврат "http://" - + НастройкиВебСервера.ИмяХоста - + ?(НастройкиВебСервера.Порт = 80, "", ":" + Формат(НастройкиВебСервера.Порт, "ЧГ=")) - + ПолныйПуть; + Возврат СтрШаблон( + "http://%1%2%3", + НастройкиВебСервера.ИмяХоста, + ?(НастройкиВебСервера.Порт = СтандартныйПорт, "", ":" + Формат(НастройкиВебСервера.Порт, "ЧГ=")), + АдресРесурса + ); КонецФункции -// Преобразует универсальную дату в дату формата rfc1123-date. -// См. https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html, п. 3.3.1. +// Форматирует дату в формат RFC 1123 для использования в HTTP-заголовках. +// Соответствует спецификации HTTP/1.1 (RFC 2616, раздел 3.3.1). // // Параметры: -// Дата - Дата, Неопределено - Если не указано, то беретеся текущая универсальная дата +// Дата - Дата, Неопределено - Дата для форматирования // // Возвращаемое значение: -// Строка -Функция ДатаHTTP(Знач Дата = Неопределено) Экспорт - - Если Дата = Неопределено Тогда - Дата = ТекущаяУниверсальнаяДата(); - КонецЕсли; +// Строка - Дата в формате "Day, DD Mon YYYY HH:MM:SS GMT" +// +Функция ДатаВФорматеHTTP(Дата) Экспорт Возврат Формат(Дата, "Л=en; ДФ='ддд, дд МММ гггг ЧЧ:мм:сс ''GMT'''"); КонецФункции -// Получает число из строки +// Безопасно преобразует строку в число. // // Параметры: -// Значение - Строка - Число строкой +// Значение - Строка - Строковое представление числа // // Возвращаемое значение: -// Число +// Число - Преобразованное число или 0, если преобразование невозможно +// Функция ВЧисло(Значение) Экспорт Возврат Новый ОписаниеТипов("Число").ПривестиЗначение(Значение); КонецФункции -// Дополняет соответствие +// Копирует все элементы из исходного соответствия в приемник. +// Существующие ключи в приемнике будут перезаписаны значениями из источника. // // Параметры: -// Приемник - Соответствие - Приемник -// Источник - Соответствие - Источник +// Приемник - Соответствие - Соответствие, в которое копируются данные +// Источник - Соответствие - Соответствие-источник данных +// Процедура ДополнитьСоответствие(Приемник, Источник) Экспорт Для Каждого Элемент Из Источник Цикл @@ -318,15 +356,18 @@ КонецПроцедуры -// Выбирает случайный элемент с учетом веса +// Выбирает случайный элемент из взвешенного списка. +// Вероятность выбора элемента пропорциональна его весу. +// Использует бинарный поиск для эффективного выбора. // // Параметры: -// Список - Массив из Структура - Взвешенный список: -// * Значение - Произвольный - Значение -// * Вес - Число - Вес +// Список - Массив из Структура - Взвешенный список элементов: +// * Значение - Произвольный - Значение элемента для возврата +// * Вес - Число - Вес элемента (чем больше, тем выше вероятность выбора) // // Возвращаемое значение: -// Произвольный - Случайное значение из списка +// Произвольный - Случайно выбранное значение с учётом весов +// Функция ВыбратьСлучайныйЭлементСУчетомВеса(Список) Экспорт ОбщийВес = 0; @@ -354,16 +395,16 @@ КонецФункции -// Разделяет строку по разделителю +// Разделяет строку на две части по первому вхождению разделителя. // // Параметры: -// Строка - Строка -// Разделитель - Строка +// Строка - Строка - Исходная строка для разделения +// Разделитель - Строка - Строка-разделитель // // Возвращаемое значение: -// Структура: -// * Лево - Строка -// * Право - Строка +// Структура - Части строки: +// * Лево - Строка - Часть до разделителя +// * Право - Строка - Часть после разделителя Функция РазделитьСтроку(Строка, Разделитель) Экспорт Результат = Новый Структура("Лево, Право", "", ""); @@ -425,6 +466,16 @@ КонецПроцедуры +// Извлекает реальный IP-адрес клиента с учётом прокси-серверов. +// Сначала проверяет заголовок X-Forwarded-For, затем использует прямой адрес. +// Удаляет IPv6-префикс ::ffff: для IPv4-адресов. +// +// Параметры: +// Запрос - ВходящийЗапрос - Объект входящего HTTP-запроса (см. winow) +// +// Возвращаемое значение: +// Строка - IP-адрес клиента (IPv4 или IPv6) +// Функция IPАдресУдаленногоУзла(Запрос) Адрес = ЗначениеЗаголовка(Запрос.Заголовки, "X-Forwarded-For"); @@ -441,6 +492,16 @@ КонецФункции +// Парсит данные multipart/form-data и разделяет их на поля формы и файлы. +// +// Параметры: +// ДанныеФормы - ДанныеСоставнойФормы, Неопределено - Данные составной формы +// +// Возвращаемое значение: +// Структура: +// * Данные - Соответствие - Текстовые поля формы +// * Файлы - Соответствие - Загруженные файлы +// Функция РазделитьДанныеФормы(ДанныеФормы) Результат = Новый Структура(); @@ -528,6 +589,17 @@ КонецФункции +// Преобразует двоичные данные в текстовое представление. +// Для бинарных данных возвращает Data URL (base64). +// Для текстовых данных возвращает UTF-8 строку. +// +// Параметры: +// ДвоичныеДанные - ДвоичныеДанные - Данные для преобразования +// ТипКонтента - Строка - MIME-тип данных +// +// Возвращаемое значение: +// Строка +// Функция ТекстовоеПредставлениеДвоичныхДанных(ДвоичныеДанные, ТипКонтента) Если ДвоичныеДанныеСодержатУправляющиеСимволы(ДвоичныеДанные) Тогда @@ -542,6 +614,15 @@ КонецФункции +// Проверяет, содержат ли двоичные данные управляющие символы (бинарные данные). +// Анализирует первые 1024 байта для определения типа содержимого. +// +// Параметры: +// ДвоичныеДанные - ДвоичныеДанные - Данные для проверки +// +// Возвращаемое значение: +// Булево - Истина, если данные содержат управляющие символы (бинарные) +// Функция ДвоичныеДанныеСодержатУправляющиеСимволы(ДвоичныеДанные) КоличествоЧитаемыхБайтов = 1024; @@ -574,6 +655,15 @@ КонецФункции +// Нормализует заголовки для сериализации в JSON. +// Для нативного веб-сервера преобразует СтроковыеЗначения в простые строки. +// +// Параметры: +// Заголовки - Соответствие, СловарьЗаголовков - Коллекция заголовков +// +// Возвращаемое значение: +// Соответствие - Нормализованные заголовки для JSON +// Функция ЗаголовкиДляJson(Заголовки) Если ЭтоНативныйВебСервер Тогда @@ -602,6 +692,14 @@ КонецФункции +// Сериализует данные в JSON с форматированием. +// +// Параметры: +// Данные - Соответствие, Структура - Данные для сериализации +// +// Возвращаемое значение: +// Строка +// Функция ВJson(Данные) ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Авто, " "); @@ -614,6 +712,16 @@ КонецФункции +// Создаёт новую структуру с указанными ключами из исходной структуры. +// Копирует только запрошенные поля. Значения null заменяются на пустые строки. +// +// Параметры: +// ИсходнаяСтруктура - Структура - Структура-источник данных +// КопируемыеКлючи - Строка - Имена копируемых полей через запятую +// +// Возвращаемое значение: +// Структура - Новая структура с запрошенными полями +// Функция СкопироватьСтруктуру(ИсходнаяСтруктура, КопируемыеКлючи) Результат = Новый Структура(КопируемыеКлючи); diff --git a/tests/HttpBin_API_test.os b/tests/HttpBin_API_test.os index 96e6220..ce20913 100644 --- a/tests/HttpBin_API_test.os +++ b/tests/HttpBin_API_test.os @@ -4,15 +4,17 @@ #Использовать 1connector #Использовать compressor #Использовать jason -#Использовать ".." +#Использовать "../src/core" -Перем HttpBin; // см. HttpBin +Перем HttpBin; // HttpBin Перем СервисЗапущен; Процедура ПередЗапускомТестов() Экспорт СервисЗапущен = Ложь; + УстановитьПеременнуюСреды("HTTPBIN_IS_TEST_MODE", "true"); + HttpBin = Новый HttpBin(); HttpBin.Запустить(); @@ -768,7 +770,7 @@ ЗадержкаСекунд = 0.5; ЗадержкаМиллисекунд = ЗадержкаСекунд * 1000; - КоэффициентПогрешности = 1.9; + КоэффициентПогрешности = 2.5; НачалоЗамера = ТекущаяУниверсальнаяДатаВМиллисекундах(); Ответ = ВызватьМетодGET("delay/" + ЗадержкаСекунд); @@ -1199,27 +1201,22 @@ Функция ВызватьМетод(Метод, АдресРсесурса, Данные = Неопределено, ДополнительныеПараметры = Неопределено) - ЗадержкуПередЧтениемСокетаПоУмолчанию = 65; - ЗадержкуПередЧтениемСокетаДляЗапросаСДанными = 400; - Если ДополнительныеПараметры = Неопределено Тогда ДополнительныеПараметры = Новый Структура(); КонецЕсли; Если Не Данные = Неопределено Тогда ДополнительныеПараметры.Вставить("Данные", Данные); - HttpBin.УстановитьЗадержкуПередЧтениемСокета(ЗадержкуПередЧтениемСокетаДляЗапросаСДанными); КонецЕсли; Ответ = КоннекторHTTP.ВызватьМетод(Метод, URL(АдресРсесурса), ДополнительныеПараметры); - HttpBin.УстановитьЗадержкуПередЧтениемСокета(ЗадержкуПередЧтениемСокетаПоУмолчанию); Возврат Ответ; КонецФункции Функция URL(АдресРсесурса) - Возврат СтрШаблон("%1/%2", HttpBin.URL(), АдресРсесурса); + Возврат HttpBin.URL(АдресРсесурса); КонецФункции Функция ПараметрыКоннектора(Заголовки = Неопределено) @@ -1238,5 +1235,5 @@ КонецФункции Функция Base64ZipФайла() - Возврат "UEsDBAoAAAAAAEAIalpdCci4BwAAAAcAAAALAAAAaHR0cGJpbi50eHRIVFRQQklOUEsBAh8ACgAAAAAAQAhqWl0JyLgHAAAABwAAAAsAJAAAAAAAAAAgAAAAAAAAAGh0dHBiaW4udHh0CgAgAAAAAAABABgAYXX04T6R2wFhdfThPpHbAdjuOJY+kdsBUEsFBgAAAAABAAEAXQAAADAAAAAAAA=="; + Возврат "UEsDBAoAAAAAAEAIalpdCci4BwAAAAcAAAALAAAAaHR0cGJpbi50eHRIVFRQQklOUEsBAh8ACgAAAAAAQAhqWl0JyLgHAAAABwAAAAsAJAAAAAAAAAAgAAAAAAAAAGh0dHBiaW4udHh0CgAgAAAAAAABABgAYXX04T6R2wFhdfThPpHbAdjuOJY+kdsBUEsFBgAAAAABAAEAXQAAADAAAAAAAA=="; // BSLLS:LineLength-off КонецФункции \ No newline at end of file diff --git a/tests/HttpBin_test.os b/tests/HttpBin_test.os index 1437e91..586e665 100644 --- a/tests/HttpBin_test.os +++ b/tests/HttpBin_test.os @@ -1,43 +1,125 @@ +// BSLLS:MagicNumber-off // BSLLS:UnusedLocalVariable-off #Использовать asserts -#Использовать ".." +#Использовать 1connector +#Использовать "../src/core" + +Перем _HttpBin; // HttpBin +Перем _ТекущийКаталог; // Строка + +Процедура ПередЗапускомТеста() Экспорт + _ТекущийКаталог = ТекущийКаталог(); +КонецПроцедуры + +Процедура ПослеЗапускаТеста() Экспорт + + УстановитьТекущийКаталог(_ТекущийКаталог); + + Если Не _HttpBin = Неопределено Тогда + _HttpBin.Остановить(); + КонецЕсли; + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисСинхронноИОстановить() Экспорт + + // Действие + _HttpBin = Новый HttpBin().Запустить(); + + // Проверка + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисАсинхронноИОстановить() Экспорт + + // Подготовка + _HttpBin = Новый HttpBin(); + + ВремяНачалаЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах(); + _HttpBin.Запустить(); + ВремяЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачалаЗапуска; + _HttpBin.Остановить(); + + Приостановить(200); + + // Действие + _HttpBin.ЗапуститьАсинх(); + + Приостановить(ВремяЗапуска); + + // Проверка + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзФайла() Экспорт + + // Подготовка + _HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры/КастомныйКонтроллер.os") + .Запустить(); + + // Действие + Ответ = ВыполнитьЗапрос("/order/add").Текст(); + + // Проверка + Ожидаем.Что(Ответ).Равно("success"); + +КонецПроцедуры &Тест -Процедура Должен_ЗапуститьСервисВФонеСОжиданиемИОстановить() Экспорт +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзКаталога() Экспорт + + // Подготовка + _HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры") + .Запустить(); + + // Действие + Ответ = ВыполнитьЗапрос("/order/add").Текст(); - HttpBin = Новый HttpBin() - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск() // Устанавливается по умолчанию при создании объекта - .Запустить() - .Остановить(); + // Проверка + Ожидаем.Что(Ответ).Равно("success"); КонецПроцедуры &Тест -Процедура Должен_ЗапуститьСервисВФонеБезОжиданияИОстановить() Экспорт +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзКаталогаСИзмененнымТекущийКаталогом() Экспорт - HttpBin = Новый HttpBin() - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск(Ложь) + // Подготовка + УстановитьТекущийКаталог("tests"); + + _HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./fixtures/КастомныеКонтроллеры") .Запустить(); - Приостановить(500); + // Действие + Ответ = ВыполнитьЗапрос("/order/add").Текст(); - HttpBin.Остановить(); + // Проверка + Ожидаем.Что(Ответ).Равно("success"); КонецПроцедуры &Тест -Процедура Должен_ЗапуститьСервисВФонеСОжиданиемИОстановитьЧерезПоделку() Экспорт +Процедура ТестДолжен_ЗапуститьСервисИзмененнымиТекущимКаталогом() Экспорт + + // Подготовка + УстановитьТекущийКаталог("tests"); - Поделка = Новый Поделка(); - Поделка.ЗапуститьПриложение(); + // Действие + _HttpBin = Новый HttpBin().Запустить(); - HttpBin = Новый HttpBin(Поделка) - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск() // Устанавливается по умолчанию при создании объекта - .Запустить() - .Остановить(); + // Проверка + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + +КонецПроцедуры -КонецПроцедуры \ No newline at end of file +Функция ВыполнитьЗапрос(АдресРесурса) + Возврат КоннекторHTTP.Get(_HttpBin.URL(АдресРесурса), , Новый Структура("Таймаут", 3)); +КонецФункции \ No newline at end of file diff --git "a/tests/fixtures/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\265\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\321\213/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" "b/tests/fixtures/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\265\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\321\213/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" new file mode 100644 index 0000000..afcf92e --- /dev/null +++ "b/tests/fixtures/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\265\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200\321\213/\320\232\320\260\321\201\321\202\320\276\320\274\320\275\321\213\320\271\320\232\320\276\320\275\321\202\321\200\320\276\320\273\320\273\320\265\321\200.os" @@ -0,0 +1,8 @@ +&Контроллер("/order") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +&ТочкаМаршрута("add") +Процедура Главная(Ответ) Экспорт + Ответ.ТелоТекст = "success"; +КонецПроцедуры \ No newline at end of file