From 85636ce88ff276ab727b155db36b9a3572be2e06 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Tue, 7 Oct 2025 23:09:25 +0300 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=D0=97=D0=B0=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D0=BA=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=BC=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=BE=D0=BC=20(#12)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- autumn-properties.json | 5 - packagedef | 2 + ...20\275\321\202\321\200\320\276\320\273.os" | 2 +- src/cmd/main.os | 9 +- ...21\201\321\202\320\270\321\202\321\214.os" | 27 --- ...21\201\321\202\320\270\321\202\321\214.os" | 36 +++ ...20\266\320\265\320\275\320\270\321\217.os" | 22 ++ .../HttpBin.os" | 213 ++++++++++++------ ...20\262\320\265\321\202\320\276\320\262.os" | 15 +- tests/HttpBin_API_test.os | 13 +- tests/HttpBin_test.os | 46 ++-- 12 files changed, 245 insertions(+), 149 deletions(-) rename "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" => "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.os" (99%) delete mode 100644 "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" create mode 100644 "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" create mode 100644 "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" diff --git a/README.md b/README.md index 05e5351..7eecdd5 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ _test.os:_ ### Swagger UI -На стартовой странице сервиса (адрес по умолчанию: `http://127.0.0.1:3334`) доступна визуальная документация API, а также возможность отправки запросов и получения ответов. +На стартовой странице сервиса (адрес по умолчанию: `http://127.0.0.1:3333`) доступна визуальная документация API, а также возможность отправки запросов и получения ответов. ## Совместимость @@ -105,7 +105,7 @@ _test.os:_ ### Класс `HttpBin` -Сервис по умолчанию запускается по адресу `127.0.0.1:3334` в фоновом режиме и с ожиданием завершения запуска сервиса.
+Сервис по умолчанию запускается по адресу `127.0.0.1:3333` в фоновом режиме и с ожиданием завершения запуска сервиса.
Класс реализован с текучим интерфейсом. | Метод | Описание | 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/packagedef b/packagedef index 2453ac7..d64c06a 100644 --- a/packagedef +++ b/packagedef @@ -16,6 +16,8 @@ .ЗависитОт("winow", "0.11.0") .ЗависитОт("compressor", "1.0.2") .ЗависитОт("1connector", "2.3.3") + .ЗависитОт("packageinfo", "1.1.1") + .ЗависитОт("fs", "1.2.0") .РазработкаЗависитОт("1testrunner") .РазработкаЗависитОт("asserts") .РазработкаЗависитОт("coverage") 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.os" similarity index 99% 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.os" index 9238112..0fb2440 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.os" @@ -1,7 +1,7 @@ #Использовать "../internal" #Использовать 1connector -Перем Помощник; // см. ПомощникПодготовкиОтветов +Перем Помощник; // ПомощникПодготовкиОтветов Перем КаталогШаблонов; // Строка #Область ТочкиМаршрута diff --git a/src/cmd/main.os b/src/cmd/main.os index 2f77450..911b586 100644 --- a/src/cmd/main.os +++ b/src/cmd/main.os @@ -1,14 +1,7 @@ #Использовать autumn #Использовать autumn-cli +#Использовать winow #Использовать "." -#Использовать "../core" - -Процедура УстановитьКорневойКаталог() - ТекущийКаталог = Новый Файл(ОбъединитьПути(ТекущийСценарий().Каталог, "../..")).ПолноеИмя; - УстановитьТекущийКаталог(ТекущийКаталог); -КонецПроцедуры - -УстановитьКорневойКаталог(); Поделка = Новый Поделка; Поделка.ЗапуститьПриложение(); \ 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..232049f --- /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,36 @@ +// BSLLS:UsingHardcodeNetworkAddress-off + +&Опция(Имя = "h host", Описание = "Имя хоста / IP адрес сервиса") +&ТСтрока +&ПоУмолчанию("127.0.0.1") +Перем _Хост; // Строка + +&Опция(Имя = "p port", Описание = "Порт сервиса") +&ТЧисло +&ПоУмолчанию(3333) +Перем _Порт; // Число + +&Пластилин("Настройки") +Перем _НастройкиВебСервера; // НастройкиВебСервера - см. winow + +&Пластилин("ЗапускательВебПриложения") +Перем _ЗапускательВебПриложения; // ЗапускательВебПриложения - см. winow + +&КомандаПриложения(Имя = "run", Описание = "Запуск сервиса") +Процедура ПриСозданииОбъекта() +КонецПроцедуры + +&ВыполнениеКоманды +Процедура Запустить() Экспорт + + _НастройкиВебСервера.РазмерБуфера = 0; + _НастройкиВебСервера.ИмяХоста = _Хост; + _НастройкиВебСервера.Порт = _Порт; + + Если ПолучитьПеременнуюСреды("HTTPBIN_IS_TEST_MODE") = "true" Тогда + _НастройкиВебСервера.ЗадержкаПередЧтениемСокета = 400; + КонецЕсли; + + _ЗапускательВебПриложения.Запустить(); + +КонецПроцедуры \ 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..49cd8cc 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,18 +1,16 @@ +// BSLLS:LatinAndCyrillicSymbolInWord-off // BSLLS:UsingHardcodeNetworkAddress-off -#Использовать autumn -#Использовать winow -#Использовать compressor #Использовать 1connector +#Использовать fs -Перем Поделка; // Ссылка на объект Поделка (autumn) -Перем ВебСервер; // Ссылка на объект ПрикладнойВебСервер (winow) -Перем НастройкиВебСервера; // Ссылка на объект Настройки (winow) -Перем ЗапускательВебПриложения; // Ссылка на объект ЗапускательВебПриложения (winow) - -Перем ЗапускатьВФоне; // Булево -Перем ОжидатьЗапуск; // Булево -Перем ТаймаутОжидания; // Количество +Перем _ЗапускатьВФоне; // Булево +Перем _ОжидатьЗапуск; // Булево +Перем _ТаймаутЗапуска; // Число +Перем _ТаймаутСоединенияHttp; // Число +Перем _ИмяХоста; // Строка +Перем _Порт; // Число +Перем _Процесс; // Процесс, Неопределено #Область ПрограммныйИнтерфейс @@ -22,14 +20,36 @@ // ЭтотОбъект Функция Запустить() Экспорт - Если ЗапускатьВФоне Тогда - ФоновыеЗадания.Выполнить(ЗапускательВебПриложения, "Запустить"); + Если Активен() Тогда + ВызватьИсключение "Процесс уже запущен"; + КонецЕсли; + + РабочийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")); + ИсполняемыйФайл = НайтиИсполняемыйФайл(); + + СтрокаКоманды = Новый Массив(); + СтрокаКоманды.Добавить(ОбернутьВДвойныеКавычки(ИсполняемыйФайл)); + СтрокаКоманды.Добавить(ОбернутьВКавычки(ТочкаВходаКонсольногоПриложения())); + СтрокаКоманды.Добавить("run"); + СтрокаКоманды.Добавить("--host"); + СтрокаКоманды.Добавить(_ИмяХоста); + СтрокаКоманды.Добавить("--port"); + СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); + СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); - Если ОжидатьЗапуск Тогда - НачатьОжиданиеЗапуска(); + _Процесс = СоздатьПроцесс(СтрокаКоманды, РабочийКаталог, , , , ПеременныеСреды()); + _Процесс.Запустить(); + + Если _ОжидатьЗапуск Тогда + НачатьОжиданиеЗапуска(); + + Если Не Активен() Тогда + ВызватьИсключение СтрШаблон("Не удалось запустить сервис по адресу %1:%2", Хост(), Порт()); КонецЕсли; - Иначе - ЗапускательВебПриложения.Запустить(); + КонецЕсли; + + Если Не _ЗапускатьВФоне Тогда + _Процесс.ОжидатьЗавершения(); КонецЕсли; Возврат ЭтотОбъект; @@ -41,8 +61,44 @@ // Возвращаемое значение: // ЭтотОбъект Функция Остановить() Экспорт - ВебСервер.Стоп(); + + Если Активен() Тогда + _Процесс.Завершить(); + КонецЕсли; + Возврат ЭтотОбъект; + +КонецФункции + +// Проверяет, что сервис отвечает на HTTP запросы без ошибок +// +// Возвращаемое значение: +// Булево +Функция Отвечает() Экспорт + + Если Не Активен() Тогда + Возврат Ложь; + КонецЕсли; + + КодСостояния = ""; + + Попытка + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутСоединенияHttp)); + КодСостояния = Ответ.КодСостояния; + Исключение + Возврат Ложь; + КонецПопытки; + + Возврат КодСостояния = КодыСостоянияHTTP.ОК_200; + +КонецФункции + +// Проверяет, что процесс сервиса активен +// +// Возвращаемое значение: +// Булево +Функция Активен() Экспорт + Возврат Не _Процесс = Неопределено И Не _Процесс.Завершен; КонецФункции // Возвращает адрес сервиса @@ -50,7 +106,7 @@ // Возвращаемое значение: // Строка Функция URL() Экспорт - Возврат СтрШаблон("http://%1:%2", НастройкиВебСервера.ИмяХоста, НастройкиВебСервера.Порт); + Возврат СтрШаблон("http://%1:%2", _ИмяХоста, _Порт); КонецФункции // Порт сервиса @@ -58,7 +114,7 @@ // Возвращаемое значение: // Строка Функция Порт() Экспорт - Возврат НастройкиВебСервера.Порт; + Возврат _Порт; КонецФункции // Устанавливает порт сервиса @@ -69,7 +125,7 @@ // Возвращаемое значение: // ЭтотОбъект Функция УстановитьПорт(Порт) Экспорт - НастройкиВебСервера.Порт = Порт; + _Порт = Порт; Возврат ЭтотОбъект; КонецФункции @@ -78,7 +134,7 @@ // Возвращаемое значение: // Строка Функция Хост() Экспорт - Возврат НастройкиВебСервера.ИмяХоста; + Возврат _ИмяХоста; КонецФункции // Устанавливает хост сервиса @@ -89,7 +145,7 @@ // Возвращаемое значение: // ЭтотОбъект Функция УстановитьХост(Хост) Экспорт - НастройкиВебСервера.ИмяХоста = Хост; + _ИмяХоста = Хост; Возврат ЭтотОбъект; КонецФункции @@ -101,11 +157,11 @@ // Возвращаемое значение: // ЭтотОбъект Функция ЗапускатьВФоне(Флаг = Истина) Экспорт - ЗапускатьВФоне = Флаг; + _ЗапускатьВФоне = Флаг; Возврат ЭтотОбъект; КонецФункции -// Ожидать завершение запуска сервиса, запущенного в фоновом режиме +// Ожидать завершение запуска сервиса // // Параметры: // Флаг - Булево @@ -113,32 +169,28 @@ // Возвращаемое значение: // ЭтотОбъект Функция ОжидатьЗапуск(Флаг = Истина) Экспорт - ОжидатьЗапуск = Флаг; + _ОжидатьЗапуск = Флаг; Возврат ЭтотОбъект; КонецФункции -// Устанавливает таймаут ожидания запуска сервиса, запущенного в фоновом режиме +// Устанавливает таймаут запуска сервиса, запущенного в фоновом режиме // // Параметры: // Таймаут - Количество - Таймаут в секундах // // Возвращаемое значение: // ЭтотОбъект -Функция УстановитьТаймаутОжидания(Таймаут) Экспорт - ТаймаутОжидания = Таймаут; +Функция УстановитьТаймаут(Таймаут) Экспорт + _ТаймаутЗапуска = Таймаут; Возврат ЭтотОбъект; КонецФункции -// Устанавливает задержку перед чтением сокета для прикладного веб-сервера (OneScript v1.9) -// -// Параметры: -// Задержка - Число - Задержка в миллисекундах (По умолчанию 65 мс) +// Таймаут запуска сервиса в секундах, запущенного в фоновом режиме // // Возвращаемое значение: -// ЭтотОбъект -Функция УстановитьЗадержкуПередЧтениемСокета(Задержка) Экспорт - НастройкиВебСервера.ЗадержкаПередЧтениемСокета = Задержка; - Возврат ЭтотОбъект; +// Число +Функция Таймаут() Экспорт + Возврат _ТаймаутЗапуска; КонецФункции #КонецОбласти @@ -147,40 +199,20 @@ // Cервис тестирования HTTP клиента. // -// Сервис по умолчанию запускается по адресу 127.0.0.1:3334 в фоновом режиме +// Сервис по умолчанию запускается по адресу 127.0.0.1:3333 в фоновом режиме // и с ожиданием завершения запуска сервиса. -// -// Параметры: -// ПоделкаОсени - Объект - Ссылка на объект Поделка (autumn) -&Желудь -Процедура ПриСозданииОбъекта(&Пластилин("Поделка") ПоделкаОсени = Неопределено) +Процедура ПриСозданииОбъекта() ХостПоУмолчанию = "127.0.0.1"; - ПортПоУмолчанию = 3334; - ТаймаутОжиданияПоУмолчанию = 5; - ЗадержкаПередЧтениемСокета = 65; - - Если ПоделкаОсени = Неопределено Тогда - УстановитьКорневойКаталог(); - - Поделка = Новый Поделка; - Поделка.ЗапуститьПриложение(); - Иначе - Поделка = ПоделкаОсени; - КонецЕсли; - - ВебСервер = Поделка.НайтиЖелудь("ВебСервер"); - НастройкиВебСервера = Поделка.НайтиЖелудь("Настройки"); - ЗапускательВебПриложения = Поделка.НайтиЖелудь("ЗапускательВебПриложения"); - - НастройкиВебСервера.РазмерБуфера = 0; + ПортПоУмолчанию = 3333; + ТаймаутЗапускаПоУмолчанию = 5; + _ТаймаутСоединенияHttp = 3; УстановитьХост(ХостПоУмолчанию); УстановитьПорт(ПортПоУмолчанию); ЗапускатьВФоне(); ОжидатьЗапуск(); - УстановитьТаймаутОжидания(ТаймаутОжиданияПоУмолчанию); - УстановитьЗадержкуПередЧтениемСокета(ЗадержкаПередЧтениемСокета); + УстановитьТаймаут(ТаймаутЗапускаПоУмолчанию); КонецПроцедуры @@ -194,7 +226,7 @@ КодСостояния = Неопределено; ТекстИсключения = ""; Попытка - Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", ТаймаутОжидания)); + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутСоединенияHttp)); КодСостояния = Ответ.КодСостояния; Исключение ТекстИсключения = СокрП(КраткоеПредставлениеОшибки(ИнформацияОбОшибке())); @@ -206,13 +238,13 @@ КонецЕсли; ПрошлоСекунд = ТекущаяУниверсальнаяДата() - ВремяНачала; - Если ПрошлоСекунд > ТаймаутОжидания Или ЗначениеЗаполнено(КодСостояния) Тогда + Если ПрошлоСекунд > _ТаймаутЗапуска Или ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон( "Не удалось запустить веб-сервер по адресу %1:%2 в течение %3 сек.", - НастройкиВебСервера.ИмяХоста, - НастройкиВебСервера.Порт, - ТаймаутОжидания); + _ИмяХоста, + _Порт, + _ТаймаутЗапуска); Если ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон("%1: @@ -232,9 +264,46 @@ КонецПроцедуры -Процедура УстановитьКорневойКаталог() - ТекущийКаталог = Новый Файл(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")).ПолноеИмя; - УстановитьТекущийКаталог(ТекущийКаталог); -КонецПроцедуры +Функция ТочкаВходаКонсольногоПриложения() + Возврат ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../cmd/main.os")); +КонецФункции + +Функция НайтиИсполняемыйФайл() + + КаталогПрограммы = КаталогПрограммы(); + + ВариантыИмени = Новый Массив(); + ВариантыИмени.Добавить("oscript.exe"); + ВариантыИмени.Добавить("oscript"); + + Для Каждого ИмяФайла Из ВариантыИмени Цикл + ИсполняемыйФайл = ОбъединитьПути(КаталогПрограммы, ИмяФайла); + Если ФС.ФайлСуществует(ИсполняемыйФайл) Тогда + Возврат ИсполняемыйФайл; + КонецЕсли; + КонецЦикла; + + ВызватьИсключение "Не удалось найти исполняемый файл oscript"; + +КонецФункции + +Функция ОбернутьВДвойныеКавычки(Строка) + Возврат СтрШаблон("%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\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..187bbc7 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,6 +1,7 @@ // BSLLS:ExportVariables-off #Использовать 1connector +#Использовать compressor Перем ТекстRobots Экспорт; // Строка Перем ASCII_Deny Экспорт; // Строка @@ -8,8 +9,8 @@ Перем АдресРедиректа; // Строка Перем ПоддерживаемыеМедиаТипы; // Массив из Строка -Перем НастройкиВебСервера; // Ссылка на объект Настройки (winow) -Перем Парсеры; // Ссылка на объект Парсеры (winow) +Перем НастройкиВебСервера; // Настройки - см. winow +Перем Парсеры; // Парсеры - см. winow Перем ЭтоНативныйВебСервер; // Булево #Область ПрограммныйИнтерфейс @@ -17,7 +18,7 @@ // Заполняет ответ json данными // // Параметры: -// Ответ - winow.Ответ - Ответ +// Ответ - Ответ - см. winow // Данные - Соответствие, Структура - Данные для передачи в тело ответа // АлгоритмСжатия - Строка - Алгоритм сжатия данных (gzip, deflate, brotli, zstd) Процедура ЗаполнитьОтветJson(Ответ, Данные, АлгоритмСжатия = Неопределено) Экспорт @@ -61,7 +62,7 @@ // Заполняет ответ по состоянию // // Параметры: -// Ответ - winow.Ответ - Ответ +// Ответ - Ответ - см. winow // КодСостояния - Число - Код состояния HTTP Процедура ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния) Экспорт @@ -109,8 +110,8 @@ // // Параметры: // Ключи - Строка - Ключи через запятую -// Запрос - winow.ВходящийЗапрос - Входящий запрос -// ДанныеФормы - winow.ДанныеСоставнойФормы - Данные составной формы (multipart) +// Запрос - ВходящийЗапрос - см. winow +// ДанныеФормы - ДанныеСоставнойФормы - Данные составной формы (multipart) (см. winow) // // Возвращаемое значение: // Структура @@ -151,7 +152,7 @@ // Данные аутентификации из входящего запроса // // Параметры: -// Запрос - winow.ВходящийЗапрос - Входящий запрос +// Запрос - ВходящийЗапрос - см. winow // // Возвращаемое значение: // Структура: diff --git a/tests/HttpBin_API_test.os b/tests/HttpBin_API_test.os index 96e6220..f4c86ff 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,20 +1201,15 @@ Функция ВызватьМетод(Метод, АдресРсесурса, Данные = Неопределено, ДополнительныеПараметры = Неопределено) - ЗадержкуПередЧтениемСокетаПоУмолчанию = 65; - ЗадержкуПередЧтениемСокетаДляЗапросаСДанными = 400; - Если ДополнительныеПараметры = Неопределено Тогда ДополнительныеПараметры = Новый Структура(); КонецЕсли; Если Не Данные = Неопределено Тогда ДополнительныеПараметры.Вставить("Данные", Данные); - HttpBin.УстановитьЗадержкуПередЧтениемСокета(ЗадержкуПередЧтениемСокетаДляЗапросаСДанными); КонецЕсли; Ответ = КоннекторHTTP.ВызватьМетод(Метод, URL(АдресРсесурса), ДополнительныеПараметры); - HttpBin.УстановитьЗадержкуПередЧтениемСокета(ЗадержкуПередЧтениемСокетаПоУмолчанию); Возврат Ответ; diff --git a/tests/HttpBin_test.os b/tests/HttpBin_test.os index 1437e91..521c5b1 100644 --- a/tests/HttpBin_test.os +++ b/tests/HttpBin_test.os @@ -1,43 +1,51 @@ // BSLLS:UnusedLocalVariable-off #Использовать asserts -#Использовать ".." +#Использовать "../src/core" + +Перем HttpBin; // HttpBin + +Процедура ПослеЗапускаТеста() Экспорт + Если Не HttpBin = Неопределено Тогда + HttpBin.Остановить(); + КонецЕсли; +КонецПроцедуры &Тест Процедура Должен_ЗапуститьСервисВФонеСОжиданиемИОстановить() Экспорт HttpBin = Новый HttpBin() - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск() // Устанавливается по умолчанию при создании объекта - .Запустить() - .Остановить(); + .ЗапускатьВФоне() + .ОжидатьЗапуск() + .Запустить(); + + Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры &Тест Процедура Должен_ЗапуститьСервисВФонеБезОжиданияИОстановить() Экспорт + // Подготовка + ВремяНачалаЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах(); + HttpBin = Новый HttpBin() - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск(Ложь) + .ЗапускатьВФоне() + .ОжидатьЗапуск() .Запустить(); - Приостановить(500); + ВремяЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачалаЗапуска; HttpBin.Остановить(); -КонецПроцедуры - -&Тест -Процедура Должен_ЗапуститьСервисВФонеСОжиданиемИОстановитьЧерезПоделку() Экспорт + Приостановить(200); + + // Действие + HttpBin.ОжидатьЗапуск(Ложь).Запустить(); - Поделка = Новый Поделка(); - Поделка.ЗапуститьПриложение(); + Приостановить(ВремяЗапуска); - HttpBin = Новый HttpBin(Поделка) - .ЗапускатьВФоне() // Устанавливается по умолчанию при создании объекта - .ОжидатьЗапуск() // Устанавливается по умолчанию при создании объекта - .Запустить() - .Остановить(); + // Проверка + Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры \ No newline at end of file From 1d72c1f975d99fc39c6d1a70a69bc4fd2bcd3ac4 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 00:24:06 +0300 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=D0=9D=D0=B0=D0=B1=D0=BB=D1=8E?= =?UTF-8?q?=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B7=D0=B0=20=D1=81=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D0=B5=D0=BC=20=D1=80=D0=BE?= =?UTF-8?q?=D0=B4=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D1=81=D0=BA=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=BF=D1=80=D0=BE=D1=86=D0=B5=D1=81=D1=81=D0=B0=20(#1?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...21\201\321\202\320\270\321\202\321\214.os" | 25 ++++++++- ...21\206\320\265\321\201\321\201\320\260.os" | 52 +++++++++++++++++++ .../HttpBin.os" | 2 + 3 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 "src/cmd/\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" 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" index 232049f..6751fc8 100644 --- "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" @@ -1,21 +1,38 @@ // BSLLS:UsingHardcodeNetworkAddress-off -&Опция(Имя = "h host", Описание = "Имя хоста / IP адрес сервиса") +&Опция( + Имя = "h host", + Описание = "Имя хоста / IP адрес сервиса" +) &ТСтрока &ПоУмолчанию("127.0.0.1") Перем _Хост; // Строка -&Опция(Имя = "p port", Описание = "Порт сервиса") +&Опция( + Имя = "p port", + Описание = "Порт сервиса" +) &ТЧисло &ПоУмолчанию(3333) Перем _Порт; // Число +&Опция( + Имя = "parrent-pid", + Описание = "PID родительского процесса" +) +&ТЧисло +&ПоУмолчанию(0) +Перем _ИдентификаторРодительскогоПроцесса; // Число + &Пластилин("Настройки") Перем _НастройкиВебСервера; // НастройкиВебСервера - см. winow &Пластилин("ЗапускательВебПриложения") Перем _ЗапускательВебПриложения; // ЗапускательВебПриложения - см. winow +&Пластилин("КонтроллерРодительскогоПроцесса") +Перем _КонтроллерРодительскогоПроцесса; // КонтроллерРодительскогоПроцесса + &КомандаПриложения(Имя = "run", Описание = "Запуск сервиса") Процедура ПриСозданииОбъекта() КонецПроцедуры @@ -31,6 +48,10 @@ _НастройкиВебСервера.ЗадержкаПередЧтениемСокета = 400; КонецЕсли; + Если _ИдентификаторРодительскогоПроцесса > 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\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/cmd/\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..62cdc82 --- /dev/null +++ "b/src/cmd/\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,52 @@ +Перем _ОжиданиеМеждуПроверками; // Число +Перем _ФоновоеЗадание; // ФоновоеЗадание, Неопределено + +&Желудь +Процедура ПриСозданииОбъекта() + _ОжиданиеМеждуПроверками = 5000; +КонецПроцедуры + +#Область ПрограммныйИнтерфейс + +// Запускает фоновое наблюдение за процессом с указанным идентификатором. +// Если процесс завершается, текущий процесс также будет автоматически завершен. +// +// Параметры: +// PID - Число - Идентификатор процесса, за которым необходимо наблюдать +Процедура НачатьНаблюдение(PID) Экспорт + + Если Не _ФоновоеЗадание = Неопределено + И _ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно Тогда + ВызватьИсключение "Наблюдение за родительским процессом уже запущено"; + КонецЕсли; + + ПараметрыМетода = Новый Массив(); + ПараметрыМетода.Добавить(PID); + + _ФоновоеЗадание = ФоновыеЗадания.Выполнить(ЭтотОбъект, "ПериодическаяПроверкаПроцесса", ПараметрыМетода); + +КонецПроцедуры + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура ПериодическаяПроверкаПроцесса(PID) Экспорт + + Пока Истина Цикл + ЗавершитьЕслиПроцессОтсутствует(PID); + Приостановить(_ОжиданиеМеждуПроверками); + КонецЦикла; + +КонецПроцедуры + +Процедура ЗавершитьЕслиПроцессОтсутствует(PID) + + Процесс = НайтиПроцессПоИдентификатору(PID); + Если Процесс = Неопределено Тогда + ТекущийПроцесс().Завершить(); + КонецЕсли; + +КонецПроцедуры + +#КонецОбласти \ 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 49cd8cc..1bcdabd 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" @@ -35,6 +35,8 @@ СтрокаКоманды.Добавить(_ИмяХоста); СтрокаКоманды.Добавить("--port"); СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); + СтрокаКоманды.Добавить("--parrent-pid"); + СтрокаКоманды.Добавить(Формат(ТекущийПроцесс().Идентификатор, "ЧГ=")); СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); _Процесс = СоздатьПроцесс(СтрокаКоманды, РабочийКаталог, , , , ПеременныеСреды()); From 9ca168bbb10386f23a229b2acae7a30b9d95eb44 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 02:47:14 +0300 Subject: [PATCH 03/14] =?UTF-8?q?feat!:=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20api=20(#14)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packagedef | 2 + ...20\275\321\202\321\200\320\276\320\273.os" | 138 ++++----- ...21\201\321\202\320\270\321\202\321\214.os" | 6 +- ...21\206\320\265\321\201\321\201\320\260.os" | 18 +- .../HttpBin.os" | 270 +++++++++++------- ...20\262\320\265\321\202\320\276\320\262.os" | 267 +++++++++++------ tests/HttpBin_API_test.os | 4 +- tests/HttpBin_test.os | 21 +- 8 files changed, 446 insertions(+), 280 deletions(-) diff --git a/packagedef b/packagedef index d64c06a..fdce502 100644 --- a/packagedef +++ b/packagedef @@ -2,6 +2,7 @@ .Версия("1.3.0") .Автор("Dmitry Ivanov") .АдресАвтора("https://github.com/Stivo182") + .АдресРепозитория("https://github.com/Stivo182/oscript-httpbin") .Описание("Сервис тестирования HTTP клиента") .ВерсияСреды("1.9.2") .ВключитьФайл("src") @@ -18,6 +19,7 @@ .ЗависитОт("1connector", "2.3.3") .ЗависитОт("packageinfo", "1.1.1") .ЗависитОт("fs", "1.2.0") + .ЗависитОт("logos", "1.7.1") .РазработкаЗависитОт("1testrunner") .РазработкаЗависитОт("asserts") .РазработкаЗависитОт("coverage") diff --git "a/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.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.os" index 0fb2440..ef2538b 100644 --- "a/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.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.os" @@ -1,8 +1,8 @@ #Использовать "../internal" #Использовать 1connector -Перем Помощник; // ПомощникПодготовкиОтветов -Перем КаталогШаблонов; // Строка +Перем _Помощник; // ПомощникПодготовкиОтветов +Перем _КаталогШаблонов; // Строка #Область ТочкиМаршрута @@ -24,7 +24,7 @@ &GET Процедура ТочкаRobots(Ответ) Экспорт - Ответ.ТелоТекст = Помощник.ТекстRobots; + Ответ.ТелоТекст = _Помощник.ТекстRobots; КонецПроцедуры @@ -32,7 +32,7 @@ &GET Процедура ТочкаDeny(Ответ) Экспорт - Ответ.ТелоТекст = Помощник.ASCII_Deny; + Ответ.ТелоТекст = _Помощник.ASCII_Deny; Ответ.Заголовки.Удалить("Content-Type"); КонецПроцедуры @@ -41,8 +41,8 @@ &GET Процедура ТочкаIP(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("origin", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + Данные = _Помощник.ПолучитьДанныеЗапроса("origin", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -52,7 +52,7 @@ Данные = Новый Структура("uuid", Строка(Новый УникальныйИдентификатор())); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -60,7 +60,7 @@ &GET Процедура ТочкаUuidN(Ответ, Знач Количество) Экспорт - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); Массив = Новый Массив(); Пока Количество > 0 Цикл @@ -70,7 +70,7 @@ Данные = Новый Структура("uuid", Массив); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -78,8 +78,8 @@ &GET Процедура ТочкаHeaders(Запрос, Ответ) Экспорт - Данные = Помощник.ПолучитьДанныеЗапроса("headers", Запрос); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + Данные = _Помощник.ПолучитьДанныеЗапроса("headers", Запрос); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -87,12 +87,12 @@ &GET Процедура ТочкаUserAgent(Запрос, Ответ) Экспорт - UserAgent = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "User-Agent"); + UserAgent = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "User-Agent"); Результат = Новый Соответствие(); Результат.Вставить("user-agent", UserAgent); - Помощник.ЗаполнитьОтветJson(Ответ, Результат); + _Помощник.ЗаполнитьОтветJson(Ответ, Результат); КонецПроцедуры @@ -105,11 +105,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 +150,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 +192,8 @@ Если Не СтрНайти(КодыСостояний, ",") Тогда - РазделеннаяСтрока = Помощник.РазделитьСтроку(КодыСостояний, ":"); - КодСостояния = Помощник.ВЧисло(РазделеннаяСтрока.Лево); + РазделеннаяСтрока = _Помощник.РазделитьСтроку(КодыСостояний, ":"); + КодСостояния = _Помощник.ВЧисло(РазделеннаяСтрока.Лево); Если КодСостояния = 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеверныйЗапрос_400); @@ -206,9 +206,9 @@ ВзвешенныйСписок = Новый Массив(); Для каждого Строка Из СтрРазделить(КодыСостояний, ",") Цикл - РазделеннаяСтрока = Помощник.РазделитьСтроку(Строка, ":"); + РазделеннаяСтрока = _Помощник.РазделитьСтроку(Строка, ":"); - КодСостояния = Помощник.ВЧисло(РазделеннаяСтрока.Лево); + КодСостояния = _Помощник.ВЧисло(РазделеннаяСтрока.Лево); Если КодСостояния = 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеверныйЗапрос_400); Ответ.ТелоТекст = "Invalid status code"; @@ -218,17 +218,17 @@ Если ПустаяСтрока(РазделеннаяСтрока.Право) Тогда Вес = 1; Иначе - Вес = Помощник.ВЧисло(РазделеннаяСтрока.Право); + Вес = _Помощник.ВЧисло(РазделеннаяСтрока.Право); КонецЕсли; ВзвешенныйСписок.Добавить(Новый Структура("Значение, Вес", КодСостояния, Вес)); КонецЦикла; - КодСостояния = Помощник.ВыбратьСлучайныйЭлементСУчетомВеса(ВзвешенныйСписок); + КодСостояния = _Помощник.ВыбратьСлучайныйЭлементСУчетомВеса(ВзвешенныйСписок); КонецЕсли; - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния); КонецПроцедуры @@ -240,10 +240,10 @@ Ответ.УстановитьТипКонтента("json"); Данные = Новый Соответствие(); - Помощник.ДополнитьСоответствие(Данные, Ответ.Заголовки); - Помощник.ДополнитьСоответствие(Данные, Запрос.ПараметрыИменные); + _Помощник.ДополнитьСоответствие(Данные, Ответ.Заголовки); + _Помощник.ДополнитьСоответствие(Данные, Запрос.ПараметрыИменные); - Помощник.ЗаполнитьОтветJson(Ответ, Данные); + _Помощник.ЗаполнитьОтветJson(Ответ, Данные); КонецПроцедуры @@ -257,7 +257,7 @@ КонецЦикла; Куки.Удалить("SessionID"); - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("cookies", Куки)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("cookies", Куки)); КонецПроцедуры @@ -309,14 +309,14 @@ &GET Процедура ТочкаBasicAuth(Запрос, Ответ, ИмяПользователя, Пароль) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "basic" И ДанныеАутентификации.ИмяПользователя = ИмяПользователя И ДанныеАутентификации.Пароль = Пароль Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); Иначе - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.НеАвторизован_401); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.НеАвторизован_401); КонецЕсли; КонецПроцедуры @@ -325,12 +325,12 @@ &GET Процедура ТочкаHiddenBasicAuth(Запрос, Ответ, ИмяПользователя, Пароль) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "basic" И ДанныеАутентификации.ИмяПользователя = ИмяПользователя И ДанныеАутентификации.Пароль = Пароль Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, user", Истина, ИмяПользователя)); Иначе Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеНайдено_404); КонецЕсли; @@ -341,10 +341,10 @@ &GET Процедура ТочкаBearer(Запрос, Ответ) Экспорт - ДанныеАутентификации = Помощник.ДанныеАутентификации(Запрос); + ДанныеАутентификации = _Помощник.ДанныеАутентификации(Запрос); Если ДанныеАутентификации.Тип = "bearer" Тогда - Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, token", Истина, ДанныеАутентификации.Токен)); + _Помощник.ЗаполнитьОтветJson(Ответ, Новый Структура("authenticated, token", Истина, ДанныеАутентификации.Токен)); Иначе Ответ.Заголовки["WWW-Authenticate"] = "Bearer"; Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеАвторизован_401); @@ -355,7 +355,7 @@ &ТочкаМаршрута("delay/{Секунд}") Процедура ТочкаDelay(Запрос, ДанныеФормы, Ответ, Секунд) Экспорт - Секунд = Помощник.ВЧисло(Секунд); + Секунд = _Помощник.ВЧисло(Секунд); Приостановить(Секунд * 1000); ПередатьПолныеДанныеВОтветJson(Запрос, ДанныеФормы, Ответ); @@ -378,11 +378,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 +396,7 @@ &GET Процедура ТочкаУстановитьCacheControl(Запрос, Ответ, Секунд) Экспорт - Секунд = Помощник.ВЧисло(Секунд); + Секунд = _Помощник.ВЧисло(Секунд); Ответ.Заголовки["Cache-Control"] = СтрШаблон("public, max-age=%1", Формат(Секунд, "ЧГ=")); @@ -408,11 +408,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 +444,11 @@ &GET Процедура ТочкаBytes(ПараметрыЗапросаИменные, Ответ, КоличествоБайт) Экспорт - КоличествоБайт = Помощник.ВЧисло(КоличествоБайт); + КоличествоБайт = _Помощник.ВЧисло(КоличествоБайт); НачальноеЧисло = ПараметрыЗапросаИменные.Получить("seed"); Если Не НачальноеЧисло = Неопределено Тогда - НачальноеЧисло = Помощник.ВЧисло(НачальноеЧисло); + НачальноеЧисло = _Помощник.ВЧисло(НачальноеЧисло); ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(НачальноеЧисло); Иначе ГенераторСлучайныхЧисел = Новый ГенераторСлучайныхЧисел(); @@ -473,8 +473,8 @@ &GET Процедура ТочкаLinks(Ответ, Количество, Смещение) Экспорт - Количество = Помощник.ВЧисло(Количество); - Смещение = Помощник.ВЧисло(Смещение); + Количество = _Помощник.ВЧисло(Количество); + Смещение = _Помощник.ВЧисло(Смещение); Html = Новый Массив(); Html.Добавить("Links"); @@ -504,7 +504,7 @@ &GET Процедура ТочкаLinksRedirect(Ответ, Количество) Экспорт - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); URL = СтрШаблон("/links/%1/0", Формат(Количество, "ЧГ=")); Ответ.Перенаправить(URL); @@ -515,7 +515,7 @@ &GET Процедура ТочкаImage(Запрос, Ответ) Экспорт - Accept = Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "Accept"); + Accept = _Помощник.ЗначениеЗаголовка(Запрос.Заголовки, "Accept"); Если Accept = "image/webp" Тогда ТочкаImageWebp(Ответ); @@ -528,7 +528,7 @@ Или Accept = "*/*" Тогда ТочкаImagePng(Ответ); Иначе - Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.Неприемлемо_406); + _Помощник.ЗаполнитьОтветПоСостоянию(Ответ, КодыСостоянияHTTP.Неприемлемо_406); КонецЕсли; КонецПроцедуры @@ -593,29 +593,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 +625,14 @@ &Контроллер("/") Процедура ПриСозданииОбъекта(&Пластилин("ПомощникПодготовкиОтветов") ПомощникПодготовкиОтветов) - Помощник = ПомощникПодготовкиОтветов; - КаталогШаблонов = "./src/app/view"; + _Помощник = ПомощникПодготовкиОтветов; + _КаталогШаблонов = "./src/app/view"; КонецПроцедуры Процедура Redirect(Количество, ПередаватьАбсолютныйПуть, Ответ) - Количество = Помощник.ВЧисло(Количество); + Количество = _Помощник.ВЧисло(Количество); Если Количество <= 0 Тогда Ответ.УстановитьСостояние(КодыСостоянияHTTP.НеНайдено_404); @@ -648,7 +648,7 @@ КонецЕсли; Если ПередаватьАбсолютныйПуть Тогда - Адрес = Помощник.ПолучитьURL(Путь); + Адрес = _Помощник.URL(Путь); Иначе Адрес = Путь; КонецЕсли; @@ -659,25 +659,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/\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" index 6751fc8..dda2906 100644 --- "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" @@ -2,7 +2,7 @@ &Опция( Имя = "h host", - Описание = "Имя хоста / IP адрес сервиса" + Описание = "Имя хоста или IP-адрес сервиса" ) &ТСтрока &ПоУмолчанию("127.0.0.1") @@ -10,14 +10,14 @@ &Опция( Имя = "p port", - Описание = "Порт сервиса" + Описание = "TCP-порт сервиса" ) &ТЧисло &ПоУмолчанию(3333) Перем _Порт; // Число &Опция( - Имя = "parrent-pid", + Имя = "parent-pid", Описание = "PID родительского процесса" ) &ТЧисло diff --git "a/src/cmd/\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/cmd/\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" index 62cdc82..3799356 100644 --- "a/src/cmd/\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/cmd/\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" @@ -1,25 +1,32 @@ -Перем _ОжиданиеМеждуПроверками; // Число -Перем _ФоновоеЗадание; // ФоновоеЗадание, Неопределено +#Использовать logos + +Перем _ОжиданиеМеждуПроверками; // Число - Миллисекунды между проверками +Перем _ФоновоеЗадание; // ФоновоеЗадание, Неопределено +Перем _Лог; // Лог &Желудь Процедура ПриСозданииОбъекта() _ОжиданиеМеждуПроверками = 5000; + _Лог = Логирование.ПолучитьЛог("oscript.lib.httpbin"); КонецПроцедуры #Область ПрограммныйИнтерфейс // Запускает фоновое наблюдение за процессом с указанным идентификатором. -// Если процесс завершается, текущий процесс также будет автоматически завершен. +// Если наблюдаемый процесс завершается, текущий процесс автоматически завершится. // // Параметры: -// PID - Число - Идентификатор процесса, за которым необходимо наблюдать +// PID - Число - Идентификатор родительского процесса для наблюдения +// Процедура НачатьНаблюдение(PID) Экспорт Если Не _ФоновоеЗадание = Неопределено И _ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно Тогда - ВызватьИсключение "Наблюдение за родительским процессом уже запущено"; + ВызватьИсключение "Невозможно запустить наблюдение: уже активно наблюдение за процессом."; КонецЕсли; + _Лог.Отладка("Запуск наблюдения за родительским процессом PID=%1", PID); + ПараметрыМетода = Новый Массив(); ПараметрыМетода.Добавить(PID); @@ -44,6 +51,7 @@ Процесс = НайтиПроцессПоИдентификатору(PID); Если Процесс = Неопределено Тогда + _Лог.Отладка("Родительский процесс PID=%1 завершён. Завершение текущего процесса", PID); ТекущийПроцесс().Завершить(); КонецЕсли; 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 1bcdabd..f9eb541 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" @@ -3,79 +3,79 @@ #Использовать 1connector #Использовать fs +#Использовать logos -Перем _ЗапускатьВФоне; // Булево -Перем _ОжидатьЗапуск; // Булево -Перем _ТаймаутЗапуска; // Число -Перем _ТаймаутСоединенияHttp; // Число -Перем _ИмяХоста; // Строка -Перем _Порт; // Число -Перем _Процесс; // Процесс, Неопределено +Перем _ТаймаутЗапуска; // Число - Таймаут запуска сервиса в секундах +Перем _ТаймаутПроверки; // Число - Таймаут HTTP-проверки доступности в секундах +Перем _ИмяХоста; // Строка - IP-адрес или доменное имя хоста +Перем _Порт; // Число - Номер TCP-порта сервиса +Перем _Процесс; // Процесс, Неопределено - Объект запущенного процесса +Перем _Лог; // Лог - Логгер для отладочной информации #Область ПрограммныйИнтерфейс -// Запускает сервис +// Запускает HTTP-сервис в синхронном режиме с ожиданием полной готовности. +// Блокирует выполнение до тех пор, пока сервис не начнет отвечать на запросы +// или не истечет таймаут запуска. // // Возвращаемое значение: -// ЭтотОбъект +// ЭтотОбъект - Для возможности цепочки вызовов +// Функция Запустить() Экспорт + Возврат ЗапуститьПроцесс(Истина); +КонецФункции - Если Активен() Тогда - ВызватьИсключение "Процесс уже запущен"; - КонецЕсли; - - РабочийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")); - ИсполняемыйФайл = НайтиИсполняемыйФайл(); - - СтрокаКоманды = Новый Массив(); - СтрокаКоманды.Добавить(ОбернутьВДвойныеКавычки(ИсполняемыйФайл)); - СтрокаКоманды.Добавить(ОбернутьВКавычки(ТочкаВходаКонсольногоПриложения())); - СтрокаКоманды.Добавить("run"); - СтрокаКоманды.Добавить("--host"); - СтрокаКоманды.Добавить(_ИмяХоста); - СтрокаКоманды.Добавить("--port"); - СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); - СтрокаКоманды.Добавить("--parrent-pid"); - СтрокаКоманды.Добавить(Формат(ТекущийПроцесс().Идентификатор, "ЧГ=")); - СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); - - _Процесс = СоздатьПроцесс(СтрокаКоманды, РабочийКаталог, , , , ПеременныеСреды()); - _Процесс.Запустить(); +// Запускает HTTP-сервис в асинхронном режиме без ожидания готовности. +// Возвращает управление немедленно после старта процесса. +// Используйте метод Отвечает() для проверки готовности сервиса. +// +// Возвращаемое значение: +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция ЗапуститьАсинх() Экспорт + Возврат ЗапуститьПроцесс(Ложь); +КонецФункции - Если _ОжидатьЗапуск Тогда - НачатьОжиданиеЗапуска(); +// Останавливает работающий HTTP-сервис. +// Если сервис не запущен, метод ничего не делает. +// +// Возвращаемое значение: +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция Остановить() Экспорт - Если Не Активен() Тогда - ВызватьИсключение СтрШаблон("Не удалось запустить сервис по адресу %1:%2", Хост(), Порт()); - КонецЕсли; - КонецЕсли; - - Если Не _ЗапускатьВФоне Тогда - _Процесс.ОжидатьЗавершения(); + Если Активен() Тогда + _Процесс.Завершить(); КонецЕсли; Возврат ЭтотОбъект; - + КонецФункции -// Останавливает сервис +// Ожидает завершения работы сервиса. +// Блокирует выполнение до тех пор, пока процесс сервиса не завершится. // // Возвращаемое значение: -// ЭтотОбъект -Функция Остановить() Экспорт +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция ОжидатьЗавершения() Экспорт - Если Активен() Тогда - _Процесс.Завершить(); + Если Не Активен() Тогда + ВызватьИсключение "Невозможно ожидать завершения: процесс сервиса не запущен"; КонецЕсли; + _Процесс.ОжидатьЗавершения(); + Возврат ЭтотОбъект; КонецФункции -// Проверяет, что сервис отвечает на HTTP запросы без ошибок +// Проверяет доступность сервиса через HTTP-запрос. +// Выполняет HEAD-запрос к корневому URL сервиса и проверяет код ответа. // // Возвращаемое значение: -// Булево +// Булево - Истина, если сервис доступен и возвращает HTTP 200 OK +// Функция Отвечает() Экспорт Если Не Активен() Тогда @@ -85,9 +85,14 @@ КодСостояния = ""; Попытка - Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутСоединенияHttp)); + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутПроверки)); КодСостояния = Ответ.КодСостояния; Исключение + _Лог.Отладка( + "Сервис недоступен по адресу %1. Ошибка: %2", + URL(), + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()) + ); Возврат Ложь; КонецПопытки; @@ -95,103 +100,100 @@ КонецФункции -// Проверяет, что процесс сервиса активен +// Проверяет, что процесс сервиса запущен и не завершен. // // Возвращаемое значение: // Булево +// Функция Активен() Экспорт Возврат Не _Процесс = Неопределено И Не _Процесс.Завершен; КонецФункции -// Возвращает адрес сервиса +// Возвращает номер TCP-порта, на котором работает сервис. // // Возвращаемое значение: -// Строка -Функция URL() Экспорт - Возврат СтрШаблон("http://%1:%2", _ИмяХоста, _Порт); -КонецФункции - -// Порт сервиса +// Число - Номер порта (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", _ИмяХоста, _Порт, АдресРесурса); + КонецФункции -// Устанавливает таймаут запуска сервиса, запущенного в фоновом режиме +// Устанавливает максимальное время ожидания запуска сервиса. +// Применяется при синхронном запуске через метод Запустить(). // // Параметры: -// Таймаут - Количество - Таймаут в секундах +// Таймаут - Число - Таймаут в секундах // // Возвращаемое значение: -// ЭтотОбъект -Функция УстановитьТаймаут(Таймаут) Экспорт +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция УстановитьТаймаутЗапуска(Таймаут) Экспорт _ТаймаутЗапуска = Таймаут; Возврат ЭтотОбъект; КонецФункции -// Таймаут запуска сервиса в секундах, запущенного в фоновом режиме +// Возвращает текущее значение таймаута запуска сервиса. // // Возвращаемое значение: -// Число -Функция Таймаут() Экспорт +// Число - Таймаут в секундах +// +Функция ТаймаутЗапуска() Экспорт Возврат _ТаймаутЗапуска; КонецФункции @@ -199,36 +201,82 @@ #Область СлужебныеПроцедурыИФункции -// Cервис тестирования HTTP клиента. +// Конструктор класса. +// +// По умолчанию сервис настроен на локальный адрес 127.0.0.1:3333 +// с таймаутом запуска 5 секунд. +// +// Параметры: +// Хост - Строка - IP-адрес или имя хоста (по умолчанию "127.0.0.1") +// Порт - Число - Номер TCP-порта (по умолчанию 3333) // -// Сервис по умолчанию запускается по адресу 127.0.0.1:3333 в фоновом режиме -// и с ожиданием завершения запуска сервиса. -Процедура ПриСозданииОбъекта() +Процедура ПриСозданииОбъекта(Хост = "127.0.0.1", Порт = 3333) // BSLLS:MagicNumber-off - ХостПоУмолчанию = "127.0.0.1"; - ПортПоУмолчанию = 3333; ТаймаутЗапускаПоУмолчанию = 5; - _ТаймаутСоединенияHttp = 3; + _ТаймаутПроверки = 3; - УстановитьХост(ХостПоУмолчанию); - УстановитьПорт(ПортПоУмолчанию); - ЗапускатьВФоне(); - ОжидатьЗапуск(); - УстановитьТаймаут(ТаймаутЗапускаПоУмолчанию); + УстановитьХост(Хост); + УстановитьПорт(Порт); + УстановитьТаймаутЗапуска(ТаймаутЗапускаПоУмолчанию); + + _Лог = Логирование.ПолучитьЛог("oscript.lib.httpbin"); КонецПроцедуры +Функция ЗапуститьПроцесс(Синхронно) + + Если Активен() Тогда + ВызватьИсключение "Невозможно запустить сервис: процесс уже запущен."; + КонецЕсли; + + РабочийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../../..")); + ИсполняемыйФайл = НайтиИсполняемыйФайл(); + + СтрокаКоманды = Новый Массив(); + СтрокаКоманды.Добавить(ОбернутьВДвойныеКавычки(ИсполняемыйФайл)); + СтрокаКоманды.Добавить(ОбернутьВКавычки(ТочкаВходаКонсольногоПриложения())); + СтрокаКоманды.Добавить("run"); + СтрокаКоманды.Добавить("--host"); + СтрокаКоманды.Добавить(_ИмяХоста); + СтрокаКоманды.Добавить("--port"); + СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); + СтрокаКоманды.Добавить("--parent-pid"); + СтрокаКоманды.Добавить(Формат(ТекущийПроцесс().Идентификатор, "ЧГ=")); + СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); + + _Лог.Отладка("Запуск процесса: %1", СтрокаКоманды); + + _Процесс = СоздатьПроцесс(СтрокаКоманды, РабочийКаталог, , , , ПеременныеСреды()); + _Процесс.Запустить(); + + Если Синхронно Тогда + НачатьОжиданиеЗапуска(); + + Если Не Активен() Тогда + ВызватьИсключение СтрШаблон("Не удалось запустить сервис по адресу %1:%2", Хост(), Порт()); + КонецЕсли; + КонецЕсли; + + Возврат ЭтотОбъект; + +КонецФункции + Процедура НачатьОжиданиеЗапуска() ЗадержкаМеждуПопытками = 100; // Миллисекунд ВремяНачала = ТекущаяУниверсальнаяДата(); + НомерПопытки = 0; Пока Истина Цикл + НомерПопытки = НомерПопытки + 1; КодСостояния = Неопределено; ТекстИсключения = ""; + + _Лог.Отладка("Проверка готовности сервиса %1. Попытка: %2", URL(), НомерПопытки); + Попытка - Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутСоединенияHttp)); + Ответ = КоннекторHTTP.Head(URL(), Новый Структура("Таймаут", _ТаймаутПроверки)); КодСостояния = Ответ.КодСостояния; Исключение ТекстИсключения = СокрП(КраткоеПредставлениеОшибки(ИнформацияОбОшибке())); @@ -236,6 +284,7 @@ КонецПопытки; Если КодСостояния = КодыСостоянияHTTP.ОК_200 Тогда + _Лог.Отладка("Сервис %1 успешно запущен и готов к работе.", URL()); Прервать; КонецЕсли; @@ -243,17 +292,24 @@ Если ПрошлоСекунд > _ТаймаутЗапуска Или ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон( - "Не удалось запустить веб-сервер по адресу %1:%2 в течение %3 сек.", + "Превышен таймаут запуска сервиса %1:%2 (%3 сек). " + + "Сервис не начал отвечать на запросы в течение отведенного времени.", _ИмяХоста, _Порт, _ТаймаутЗапуска); Если ЗначениеЗаполнено(КодСостояния) Тогда ТекстОшибки = СтрШаблон("%1: - |%2", ТекстОшибки, КодыСостоянияHTTP.Представление(КодСостояния)); + |%2", + ТекстОшибки, + КодыСостоянияHTTP.Представление(КодСостояния) + ); Иначе ТекстОшибки = СтрШаблон("%1: - |%2", ТекстОшибки, ТекстИсключения); + |%2", + ТекстОшибки, + ТекстИсключения + ); КонецЕсли; ВызватьИсключение ТекстОшибки; @@ -285,7 +341,7 @@ КонецЕсли; КонецЦикла; - ВызватьИсключение "Не удалось найти исполняемый файл oscript"; + ВызватьИсключение СтрШаблон("Не найден исполняемый файл oscript в каталоге: %1", КаталогПрограммы); КонецФункции 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 187bbc7..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" @@ -3,67 +3,70 @@ #Использовать 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-статуса +// Процедура ЗаполнитьОтветПоСостоянию(Ответ, КодСостояния) Экспорт Ответ.УстановитьСостояние(КодСостояния); @@ -106,15 +109,32 @@ КонецПроцедуры -// Данные входящего запроса +// Извлекает и структурирует данные из входящего HTTP-запроса. +// Возвращает только указанные ключи из полного набора данных запроса. // // Параметры: -// Ключи - Строка - Ключи через запятую -// Запрос - ВходящийЗапрос - см. winow -// ДанныеФормы - ДанныеСоставнойФормы - Данные составной формы (multipart) (см. winow) +// Ключи - Строка - Имена возвращаемых полей через запятую. +// Доступные поля: 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) +// Функция ПолучитьДанныеЗапроса(Ключи, Запрос, ДанныеФормы = Неопределено) Экспорт ДанныеФормы = РазделитьДанныеФормы(ДанныеФормы); @@ -129,7 +149,7 @@ КонецЕсли; ДанныеЗапроса = Новый Структура(); - ДанныеЗапроса.Вставить("url", ПолучитьURL(Запрос.ПолныйПуть)); + ДанныеЗапроса.Вставить("url", URL(Запрос.ПолныйПуть)); ДанныеЗапроса.Вставить("method", Запрос.Метод); ДанныеЗапроса.Вставить("args", Запрос.ПараметрыИменные); ДанныеЗапроса.Вставить("headers", ЗаголовкиДляJson(Запрос.Заголовки)); @@ -149,20 +169,24 @@ КонецФункции -// Данные аутентификации из входящего запроса +// Извлекает данные аутентификации из заголовка Authorization. +// Поддерживает схемы Basic и Bearer. // // Параметры: -// Запрос - ВходящийЗапрос - см. winow +// Запрос - ВходящийЗапрос - Объект входящего HTTP-запроса (см. winow) // // Возвращаемое значение: -// Структура: -// Общее: -// * Тип - Строка - Тип аутентификации (basic, bearer) -// Basic: +// Структура - Данные аутентификации: +// Общие поля: +// * Тип - Строка - Схема аутентификации ("basic", "bearer" или пустая строка) +// +// Для Basic-аутентификации (Тип = "basic"): // * ИмяПользователя - Строка, Неопределено - Имя пользователя // * Пароль - Строка, Неопределено - Пароль -// Bearer: -// * Токен - Строка - Токен +// +// Для Bearer-токена (Тип = "bearer"): +// * Токен - Строка - Значение токена +// Функция ДанныеАутентификации(Запрос) Экспорт Данные = Новый Структура("Тип", ""); @@ -205,14 +229,15 @@ КонецФункции -// Значение заголовка по имени +// Извлекает значение HTTP-заголовка по имени (регистронезависимый поиск). // // Параметры: // Заголовки - Соответствие, СловарьЗаголовков - Коллекция заголовков -// Имя - Строка - Имя заголовка +// Имя - Строка - Имя заголовка для поиска (регистр не важен) // // Возвращаемое значение: -// Строка, Неопределено +// Строка, Соответствие, Неопределено +// Функция ЗначениеЗаголовка(Заголовки, Знач Имя) Экспорт Имя = НРег(Имя); @@ -232,13 +257,15 @@ КонецФункции -// Парсит многозначный заголовок +// Парсит многозначный HTTP-заголовок (например, If-Match, If-None-Match). +// Обрабатывает значения с кавычками и опциональным префиксом W/ (weak ETag). // // Параметры: -// Значение - Строка - Значение заголовка +// Значение - Строка - Значение заголовка с несколькими значениями через запятую // // Возвращаемое значение: -// Массив из Строка - Массив значений +// Массив из Строка - Массив извлечённых значений +// Функция РаспаристьМногозначныйЗаголовок(Значение) Экспорт Результат = Новый Массив(); @@ -261,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, если преобразование невозможно +// Функция ВЧисло(Значение) Экспорт Возврат Новый ОписаниеТипов("Число").ПривестиЗначение(Значение); КонецФункции -// Дополняет соответствие +// Копирует все элементы из исходного соответствия в приемник. +// Существующие ключи в приемнике будут перезаписаны значениями из источника. // // Параметры: -// Приемник - Соответствие - Приемник -// Источник - Соответствие - Источник +// Приемник - Соответствие - Соответствие, в которое копируются данные +// Источник - Соответствие - Соответствие-источник данных +// Процедура ДополнитьСоответствие(Приемник, Источник) Экспорт Для Каждого Элемент Из Источник Цикл @@ -319,15 +356,18 @@ КонецПроцедуры -// Выбирает случайный элемент с учетом веса +// Выбирает случайный элемент из взвешенного списка. +// Вероятность выбора элемента пропорциональна его весу. +// Использует бинарный поиск для эффективного выбора. // // Параметры: -// Список - Массив из Структура - Взвешенный список: -// * Значение - Произвольный - Значение -// * Вес - Число - Вес +// Список - Массив из Структура - Взвешенный список элементов: +// * Значение - Произвольный - Значение элемента для возврата +// * Вес - Число - Вес элемента (чем больше, тем выше вероятность выбора) // // Возвращаемое значение: -// Произвольный - Случайное значение из списка +// Произвольный - Случайно выбранное значение с учётом весов +// Функция ВыбратьСлучайныйЭлементСУчетомВеса(Список) Экспорт ОбщийВес = 0; @@ -355,16 +395,16 @@ КонецФункции -// Разделяет строку по разделителю +// Разделяет строку на две части по первому вхождению разделителя. // // Параметры: -// Строка - Строка -// Разделитель - Строка +// Строка - Строка - Исходная строка для разделения +// Разделитель - Строка - Строка-разделитель // // Возвращаемое значение: -// Структура: -// * Лево - Строка -// * Право - Строка +// Структура - Части строки: +// * Лево - Строка - Часть до разделителя +// * Право - Строка - Часть после разделителя Функция РазделитьСтроку(Строка, Разделитель) Экспорт Результат = Новый Структура("Лево, Право", "", ""); @@ -426,6 +466,16 @@ КонецПроцедуры +// Извлекает реальный IP-адрес клиента с учётом прокси-серверов. +// Сначала проверяет заголовок X-Forwarded-For, затем использует прямой адрес. +// Удаляет IPv6-префикс ::ffff: для IPv4-адресов. +// +// Параметры: +// Запрос - ВходящийЗапрос - Объект входящего HTTP-запроса (см. winow) +// +// Возвращаемое значение: +// Строка - IP-адрес клиента (IPv4 или IPv6) +// Функция IPАдресУдаленногоУзла(Запрос) Адрес = ЗначениеЗаголовка(Запрос.Заголовки, "X-Forwarded-For"); @@ -442,6 +492,16 @@ КонецФункции +// Парсит данные multipart/form-data и разделяет их на поля формы и файлы. +// +// Параметры: +// ДанныеФормы - ДанныеСоставнойФормы, Неопределено - Данные составной формы +// +// Возвращаемое значение: +// Структура: +// * Данные - Соответствие - Текстовые поля формы +// * Файлы - Соответствие - Загруженные файлы +// Функция РазделитьДанныеФормы(ДанныеФормы) Результат = Новый Структура(); @@ -529,6 +589,17 @@ КонецФункции +// Преобразует двоичные данные в текстовое представление. +// Для бинарных данных возвращает Data URL (base64). +// Для текстовых данных возвращает UTF-8 строку. +// +// Параметры: +// ДвоичныеДанные - ДвоичныеДанные - Данные для преобразования +// ТипКонтента - Строка - MIME-тип данных +// +// Возвращаемое значение: +// Строка +// Функция ТекстовоеПредставлениеДвоичныхДанных(ДвоичныеДанные, ТипКонтента) Если ДвоичныеДанныеСодержатУправляющиеСимволы(ДвоичныеДанные) Тогда @@ -543,6 +614,15 @@ КонецФункции +// Проверяет, содержат ли двоичные данные управляющие символы (бинарные данные). +// Анализирует первые 1024 байта для определения типа содержимого. +// +// Параметры: +// ДвоичныеДанные - ДвоичныеДанные - Данные для проверки +// +// Возвращаемое значение: +// Булево - Истина, если данные содержат управляющие символы (бинарные) +// Функция ДвоичныеДанныеСодержатУправляющиеСимволы(ДвоичныеДанные) КоличествоЧитаемыхБайтов = 1024; @@ -575,6 +655,15 @@ КонецФункции +// Нормализует заголовки для сериализации в JSON. +// Для нативного веб-сервера преобразует СтроковыеЗначения в простые строки. +// +// Параметры: +// Заголовки - Соответствие, СловарьЗаголовков - Коллекция заголовков +// +// Возвращаемое значение: +// Соответствие - Нормализованные заголовки для JSON +// Функция ЗаголовкиДляJson(Заголовки) Если ЭтоНативныйВебСервер Тогда @@ -603,6 +692,14 @@ КонецФункции +// Сериализует данные в JSON с форматированием. +// +// Параметры: +// Данные - Соответствие, Структура - Данные для сериализации +// +// Возвращаемое значение: +// Строка +// Функция ВJson(Данные) ПараметрыЗаписиJSON = Новый ПараметрыЗаписиJSON(ПереносСтрокJSON.Авто, " "); @@ -615,6 +712,16 @@ КонецФункции +// Создаёт новую структуру с указанными ключами из исходной структуры. +// Копирует только запрошенные поля. Значения null заменяются на пустые строки. +// +// Параметры: +// ИсходнаяСтруктура - Структура - Структура-источник данных +// КопируемыеКлючи - Строка - Имена копируемых полей через запятую +// +// Возвращаемое значение: +// Структура - Новая структура с запрошенными полями +// Функция СкопироватьСтруктуру(ИсходнаяСтруктура, КопируемыеКлючи) Результат = Новый Структура(КопируемыеКлючи); diff --git a/tests/HttpBin_API_test.os b/tests/HttpBin_API_test.os index f4c86ff..ce20913 100644 --- a/tests/HttpBin_API_test.os +++ b/tests/HttpBin_API_test.os @@ -1216,7 +1216,7 @@ КонецФункции Функция URL(АдресРсесурса) - Возврат СтрШаблон("%1/%2", HttpBin.URL(), АдресРсесурса); + Возврат HttpBin.URL(АдресРсесурса); КонецФункции Функция ПараметрыКоннектора(Заголовки = Неопределено) @@ -1235,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 521c5b1..8cd4da2 100644 --- a/tests/HttpBin_test.os +++ b/tests/HttpBin_test.os @@ -12,36 +12,29 @@ КонецПроцедуры &Тест -Процедура Должен_ЗапуститьСервисВФонеСОжиданиемИОстановить() Экспорт +Процедура ТестДолжен_ЗапуститьСервисСинхронноИОстановить() Экспорт - HttpBin = Новый HttpBin() - .ЗапускатьВФоне() - .ОжидатьЗапуск() - .Запустить(); + HttpBin = Новый HttpBin().Запустить(); Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры &Тест -Процедура Должен_ЗапуститьСервисВФонеБезОжиданияИОстановить() Экспорт +Процедура ТестДолжен_ЗапуститьСервисАсинхронноИОстановить() Экспорт // Подготовка + HttpBin = Новый HttpBin(); + ВремяНачалаЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах(); - - HttpBin = Новый HttpBin() - .ЗапускатьВФоне() - .ОжидатьЗапуск() - .Запустить(); - + HttpBin.Запустить(); ВремяЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачалаЗапуска; - HttpBin.Остановить(); Приостановить(200); // Действие - HttpBin.ОжидатьЗапуск(Ложь).Запустить(); + HttpBin.ЗапуститьАсинх(); Приостановить(ВремяЗапуска); From f3d695831e77da6ae58744d5612554e39cbb0855 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 03:23:04 +0300 Subject: [PATCH 04/14] docs: Update README.md --- README.md | 114 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 7eecdd5..f1004a8 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,14 @@ [![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) @@ -27,16 +27,25 @@ 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` | + +### Тестирование с [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 +65,77 @@ _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:3333`) доступна визуальная документация 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:3333` в фоновом режиме и с ожиданием завершения запуска сервиса.
-Класс реализован с текучим интерфейсом. +Класс предназначен для управления локальным HTTP-сервисом. +Реализован с использованием текучего интерфейса. + +#### Синтаксис + +`Новый HttpBin(<Хост>, <Порт>)` + +#### Параметры + +<Хост> (необязательный)
+Тип: _Строка_.
+IP-адрес или имя хоста.
+По умолчанию: _127.0.0.1_ + +<Порт> (необязательный)
+Тип: _Строка_.
+Номер TCP-порта.
+По умолчанию: _3333_ + +#### Методы | Метод | Описание | | --- | --- | -| `Запустить()` | Запускает сервис | -| `Остановить()` | Останавливает сервис | -| `URL()` | URL сервиса | -| `Хост()` | Хост сервиса | -| `Порт()` | Порт сервиса | -| `УстановитьХост(<Хост>)` | Устанавливает хост сервиса | -| `УстановитьПорт(<Порт>)` | Устанавливает порт сервиса | -| `ЗапускатьВФоне(<Флаг>)` | Запуск сервиса будет выполнен в фоновом режиме | -| `ОжидатьЗапуск(<Флаг>)` | Ожидать завершение запуска сервиса, запущенного в фоновом режиме | -| `УстановитьТаймаутОжидания(<Таймаут>)` | Устанавливает таймаут ожидания запуска сервиса, запущенного в фоновом режиме | +| `Запустить()` | Запускает сервис в синхронном режиме с ожиданием полной готовности. | +| `ЗапуститьАсинх()` | Запускает сервис в асинхронном режиме без ожидания готовности. | +| `Остановить()` | Останавливает сервис. | +| `ОжидатьЗавершения()` | Ожидает завершения работы сервиса, приостанавливая выполнение текущего потока. | +| `Отвечает()` | Проверяет доступность сервиса через HTTP-запрос. | +| `Активен()` | Проверяет, что процесс сервиса запущен и не завершен. | +| `Порт()` | Возвращает номер TCP-порта, на котором работает сервис. | +| `УстановитьПорт(<Порт>)` | Устанавливает TCP-порт для запуска сервиса. | +| `Хост()` | Возвращает имя хоста или IP-адрес сервиса. | +| `УстановитьХост(<Хост>)` | Устанавливает имя хоста или IP-адрес для запуска сервиса. | +| `URL(<АдресРесурса>)` | Формирует полный URL-адрес сервиса с опциональным путем к ресурсу. | +| `ТаймаутЗапуска()` | Возвращает текущее значение таймаута запуска сервиса. | +| `УстановитьТаймаутЗапуска(<Таймаут>)` | Устанавливает максимальное время ожидания запуска сервиса. Применяется при синхронном запуске. | ## Ограничения -На данный момент нет поддержки https. +- **Отсутствие поддержки HTTPS**: Сервис работает только по протоколу HTTP. ## Сравнение с httpbin.org From b4ea2c033e7a37608f37988a104880ffad13f8f7 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 03:28:18 +0300 Subject: [PATCH 05/14] =?UTF-8?q?build:=20=D0=91=D0=B0=D0=BC=D0=BF=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=B4=D0=BE=202.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib.config | 3 --- packagedef | 9 ++++----- 2 files changed, 4 insertions(+), 8 deletions(-) delete mode 100644 lib.config 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 fdce502..b1ab5e2 100644 --- a/packagedef +++ b/packagedef @@ -1,15 +1,14 @@ Описание.Имя("httpbin") - .Версия("1.3.0") + .Версия("2.0.0") .Автор("Dmitry Ivanov") .АдресАвтора("https://github.com/Stivo182") .АдресРепозитория("https://github.com/Stivo182/oscript-httpbin") - .Описание("Сервис тестирования HTTP клиента") + .Описание("Сервис тестирования HTTP клиентов") .ВерсияСреды("1.9.2") .ВключитьФайл("src") - .ВключитьФайл("tests") .ВключитьФайл("packagedef") - .ВключитьФайл("lib.config") .ВключитьФайл("autumn-properties.json") + .ВключитьФайл("appsettings.json") .ВключитьФайл("README.md") .ВключитьФайл("LICENSE") .ЗависитОт("autumn", "4.3.11") @@ -24,6 +23,6 @@ .РазработкаЗависитОт("asserts") .РазработкаЗависитОт("coverage") .РазработкаЗависитОт("1commands") - .РазработкаЗависитОт("fs") .РазработкаЗависитОт("jason") + .ОпределяетКласс("HttpBin", "src/core/Классы/HttpBin.os") .ИсполняемыйФайл("src/cmd/main.os", "httpbin") \ No newline at end of file From f1b4be88c7dd08388686321261220ee2b309e292 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 03:28:40 +0300 Subject: [PATCH 06/14] =?UTF-8?q?chore:=20=D0=A0=D0=B5=D1=84=D0=B0=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20.gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 From d4257148ad453b9574319ac33974ff8448f7f5b5 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 03:30:05 +0300 Subject: [PATCH 07/14] docs: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f1004a8..9bf040d 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ IP-адрес или имя хоста.
По умолчанию: _127.0.0.1_ <Порт> (необязательный)
-Тип: _Строка_.
+Тип: _Число_.
Номер TCP-порта.
По умолчанию: _3333_ From 3d94b803737378021fad8f39e5a27d17800d57d3 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 09:05:02 +0300 Subject: [PATCH 08/14] =?UTF-8?q?docs:=20=D0=A3=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BE=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- packagedef | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9bf040d..a1b891c 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ [![Тестирование](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) -Локальный сервис для тестирования 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) diff --git a/packagedef b/packagedef index b1ab5e2..d1f3f56 100644 --- a/packagedef +++ b/packagedef @@ -3,7 +3,7 @@ .Автор("Dmitry Ivanov") .АдресАвтора("https://github.com/Stivo182") .АдресРепозитория("https://github.com/Stivo182/oscript-httpbin") - .Описание("Сервис тестирования HTTP клиентов") + .Описание("Сервис для тестирования и отладки HTTP-запросов") .ВерсияСреды("1.9.2") .ВключитьФайл("src") .ВключитьФайл("packagedef") From 95203ca2a0bf73df7850893ee902e9bcca6ccd7a Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 16:14:05 +0300 Subject: [PATCH 09/14] =?UTF-8?q?feat:=20=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=BA=D0=B0=20=D0=BA=D0=B0=D1=81=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D1=8D=D0=BD=D0=B4=D0=BF=D0=BE=D0=B8=D0=BD?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20(#15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 47 ++++++++++- ...20\276\320\273\320\273\320\265\321\200.os" | 1 - src/cmd/main.os | 5 +- ...21\201\321\202\320\270\321\202\321\214.os" | 36 ++++---- .../HttpBin.os" | 41 +++++++-- ...21\200\320\262\320\270\321\201\320\260.os" | 43 ++++++++++ ...21\206\320\265\321\201\321\201\320\260.os" | 0 ...20\273\320\265\321\200\320\276\320\262.os" | 83 +++++++++++++++++++ tests/HttpBin_test.os | 36 ++++++++ ...20\276\320\273\320\273\320\265\321\200.os" | 8 ++ 10 files changed, 276 insertions(+), 24 deletions(-) rename "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.os" => "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" (99%) create mode 100644 "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" rename "src/cmd/\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" => "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" (100%) create mode 100644 "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" create mode 100644 "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" diff --git a/README.md b/README.md index a1b891c..eb71a8e 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ * 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) ## Установка @@ -39,6 +40,7 @@ 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) @@ -132,6 +134,47 @@ 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' +``` ## Ограничения diff --git "a/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.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 99% rename from "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.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 ef2538b..1bd1114 100644 --- "a/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.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,4 +1,3 @@ -#Использовать "../internal" #Использовать 1connector Перем _Помощник; // ПомощникПодготовкиОтветов diff --git a/src/cmd/main.os b/src/cmd/main.os index 911b586..c0642e9 100644 --- a/src/cmd/main.os +++ b/src/cmd/main.os @@ -1,7 +1,10 @@ #Использовать autumn #Использовать autumn-cli -#Использовать winow #Использовать "." +#Использовать "../internal" + +ПодключательКастомныхКонтроллеров = Новый ПодключательКастомныхКонтроллеров(); +ПодключательКастомныхКонтроллеров.НайтиИПодключить(); Поделка = Новый Поделка; Поделка.ЗапуститьПриложение(); \ 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" index dda2906..613ed0e 100644 --- "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" @@ -1,5 +1,7 @@ // BSLLS:UsingHardcodeNetworkAddress-off +#Область Опции + &Опция( Имя = "h host", Описание = "Имя хоста или IP-адрес сервиса" @@ -24,15 +26,27 @@ &ПоУмолчанию(0) Перем _ИдентификаторРодительскогоПроцесса; // Число -&Пластилин("Настройки") -Перем _НастройкиВебСервера; // НастройкиВебСервера - см. winow +&Опция( + Имя = "routes-handlers", + Описание = "Путь к файлу или каталогу кастомных контроллеров" +) +&ТМассивСтрок +Перем _КастомныеКонтроллеры; // BSLLS:UnusedLocalVariable-off + +#КонецОбласти -&Пластилин("ЗапускательВебПриложения") -Перем _ЗапускательВебПриложения; // ЗапускательВебПриложения - см. winow +#Область ОписаниеПеременных &Пластилин("КонтроллерРодительскогоПроцесса") Перем _КонтроллерРодительскогоПроцесса; // КонтроллерРодительскогоПроцесса +&Пластилин("ЗапускательСервиса") +Перем _ЗапускательСервиса; // ЗапускательСервиса + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + &КомандаПриложения(Имя = "run", Описание = "Запуск сервиса") Процедура ПриСозданииОбъекта() КонецПроцедуры @@ -40,18 +54,12 @@ &ВыполнениеКоманды Процедура Запустить() Экспорт - _НастройкиВебСервера.РазмерБуфера = 0; - _НастройкиВебСервера.ИмяХоста = _Хост; - _НастройкиВебСервера.Порт = _Порт; - - Если ПолучитьПеременнуюСреды("HTTPBIN_IS_TEST_MODE") = "true" Тогда - _НастройкиВебСервера.ЗадержкаПередЧтениемСокета = 400; - КонецЕсли; - Если _ИдентификаторРодительскогоПроцесса > 0 Тогда _КонтроллерРодительскогоПроцесса.НачатьНаблюдение(_ИдентификаторРодительскогоПроцесса); КонецЕсли; - _ЗапускательВебПриложения.Запустить(); + _ЗапускательСервиса.Запустить(_Хост, _Порт); + +КонецПроцедуры -КонецПроцедуры \ No newline at end of file +#КонецОбласти \ 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 f9eb541..e04edc8 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" @@ -5,12 +5,13 @@ #Использовать fs #Использовать logos -Перем _ТаймаутЗапуска; // Число - Таймаут запуска сервиса в секундах -Перем _ТаймаутПроверки; // Число - Таймаут HTTP-проверки доступности в секундах -Перем _ИмяХоста; // Строка - IP-адрес или доменное имя хоста -Перем _Порт; // Число - Номер TCP-порта сервиса -Перем _Процесс; // Процесс, Неопределено - Объект запущенного процесса -Перем _Лог; // Лог - Логгер для отладочной информации +Перем _ТаймаутЗапуска; // Число - Таймаут запуска сервиса в секундах +Перем _ТаймаутПроверки; // Число - Таймаут HTTP-проверки доступности в секундах +Перем _ИмяХоста; // Строка - IP-адрес или доменное имя хоста +Перем _Порт; // Число - Номер TCP-порта сервиса +Перем _РасположениеКонтроллеров; // Строка, Неопределено - Путь к папке или файлу с кастомными контроллерами +Перем _Процесс; // Процесс, Неопределено - Объект запущенного процесса +Перем _Лог; // Лог - Логгер для отладочной информации #Область ПрограммныйИнтерфейс @@ -197,6 +198,28 @@ Возврат _ТаймаутЗапуска; КонецФункции +// Устанавливает путь к папке или файлу с кастомными контроллерами, определяющими маршруты сервиса. +// +// Параметры: +// Расположение - Строка - Путь к папке или файлу с контроллерами (например, "./controllers" или "./МойКонтроллер.os") +// +// Возвращаемое значение: +// ЭтотОбъект - Для возможности цепочки вызовов +// +Функция УстановитьРасположениеКонтроллеров(Расположение) Экспорт + _РасположениеКонтроллеров = Расположение; + Возврат ЭтотОбъект; +КонецФункции + +// Возвращает текущий путь к папке или файлу с кастомными контроллерами. +// +// Возвращаемое значение: +// Строка, Неопределено +// +Функция РасположениеКонтроллеров() Экспорт + Возврат _РасположениеКонтроллеров; +КонецФункции + #КонецОбласти #Область СлужебныеПроцедурыИФункции @@ -242,6 +265,12 @@ СтрокаКоманды.Добавить(Формат(_Порт, "ЧГ=")); СтрокаКоманды.Добавить("--parent-pid"); СтрокаКоманды.Добавить(Формат(ТекущийПроцесс().Идентификатор, "ЧГ=")); + + Если ЗначениеЗаполнено(_РасположениеКонтроллеров) Тогда + СтрокаКоманды.Добавить("--routes-handlers"); + СтрокаКоманды.Добавить(ОбернутьВКавычки(_РасположениеКонтроллеров)); + КонецЕсли; + СтрокаКоманды = СтрСоединить(СтрокаКоманды, " "); _Лог.Отладка("Запуск процесса: %1", СтрокаКоманды); 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/cmd/\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" similarity index 100% rename from "src/cmd/\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" rename to "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" 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..cc54481 --- /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,83 @@ +#Использовать fs + +#Область ПрограммныйИнтерфейс + +// Находит и подключает кастомные контроллеры из путей, указанных в аргументах командной строки. +// +Процедура НайтиИПодключить() Экспорт + + МассивФайлов = НайтиФайлыИзПереданныхАргументов(); + + Для Каждого ПолноеИмя Из МассивФайлов Цикл + + Если ФС.КаталогСуществует(ПолноеИмя) Тогда + ПодключитьИзКаталога(ПолноеИмя); + ИначеЕсли ФС.ФайлСуществует(ПолноеИмя) Тогда + Подключить(ПолноеИмя); + Иначе + ВызватьИсключение СтрШаблон("Не удалось подключить контроллер '%1': файл не существует", ПолноеИмя); + КонецЕсли; + + КонецЦикла; + +КонецПроцедуры + +// Подключает контроллер из указанного файла. +// +// Параметры: +// ПолноеИмя - Строка - Полный путь к файлу контроллера (.os) +// +Процедура Подключить(ПолноеИмя) Экспорт + + Имя = Новый Файл(ПолноеИмя).ИмяБезРасширения; + + Попытка + ПодключитьСценарий(ПолноеИмя, Имя); + Исключение + ВызватьИсключение СтрШаблон( + "Не удалось подключить контроллер '%1': %2", + ПолноеИмя, + КраткоеПредставлениеОшибки(ИнформацияОбОшибке()) + ); + КонецПопытки; + +КонецПроцедуры + +#КонецОбласти + +#Область СлужебныеПроцедурыИФункции + +Процедура ПодключитьИзКаталога(Каталог) + + Файлы = НайтиФайлы(Каталог, "*.os"); + + Для Каждого Файл Из Файлы Цикл + Подключить(Файл.ПолноеИмя); + КонецЦикла; + +КонецПроцедуры + +Функция НайтиФайлыИзПереданныхАргументов() + + Файлы = Новый Массив(); + + НайденКлюч = Ложь; + Для Каждого Значение Из АргументыКоманднойСтроки Цикл + + Если НайденКлюч Тогда + Файлы.Добавить(Значение); + НайденКлюч = Ложь; + Продолжить; + КонецЕсли; + + Если Значение = "--routes-handlers" Тогда + НайденКлюч = Истина; + КонецЕсли; + + КонецЦикла; + + Возврат Файлы; + +КонецФункции + +#КонецОбласти \ No newline at end of file diff --git a/tests/HttpBin_test.os b/tests/HttpBin_test.os index 8cd4da2..151c36a 100644 --- a/tests/HttpBin_test.os +++ b/tests/HttpBin_test.os @@ -1,6 +1,8 @@ +// BSLLS:MagicNumber-off // BSLLS:UnusedLocalVariable-off #Использовать asserts +#Использовать 1connector #Использовать "../src/core" Перем HttpBin; // HttpBin @@ -14,8 +16,10 @@ &Тест Процедура ТестДолжен_ЗапуститьСервисСинхронноИОстановить() Экспорт + // Действие HttpBin = Новый HttpBin().Запустить(); + // Проверка Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры @@ -41,4 +45,36 @@ // Проверка Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзФайла() Экспорт + + // Подготовка + HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры/КастомныйКонтроллер.os") + .Запустить(); + + // Действие + Ответ = КоннекторHTTP.Get(HttpBin.URL("/order/add"), , Новый Структура("Таймаут", 3)).Текст(); + + // Проверка + Ожидаем.Что(Ответ).Равно("success"); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзКаталога() Экспорт + + // Подготовка + HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры") + .Запустить(); + + // Действие + Ответ = КоннекторHTTP.Get(HttpBin.URL("/order/add"), , Новый Структура("Таймаут", 3)).Текст(); + + // Проверка + Ожидаем.Что(Ответ).Равно("success"); + КонецПроцедуры \ 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 From 069a4cb0bf29d06fadb46956cbefd34affce4c16 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 16:34:00 +0300 Subject: [PATCH 10/14] =?UTF-8?q?fix:=20=D0=9D=D0=BE=D1=80=D0=BC=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=BF=D1=83=D1=82?= =?UTF-8?q?=D0=B8=20=D0=BA=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB?= =?UTF-8?q?=D0=BB=D0=B5=D1=80=D0=B0=D0=BC.=20=D0=A3=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BA=D0=B0=20=D1=82=D0=B5=D0=BA.=20=D0=BA?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cmd/main.os | 4 ++++ .../HttpBin.os" | 2 +- ...0\273\320\273\320\265\321\200\320\276\320\262.os" | 12 +++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/cmd/main.os b/src/cmd/main.os index c0642e9..7c5f6c9 100644 --- a/src/cmd/main.os +++ b/src/cmd/main.os @@ -1,10 +1,14 @@ #Использовать autumn #Использовать autumn-cli +#Использовать fs #Использовать "." #Использовать "../internal" ПодключательКастомныхКонтроллеров = Новый ПодключательКастомныхКонтроллеров(); ПодключательКастомныхКонтроллеров.НайтиИПодключить(); +ТекущийКаталог = ФС.НормализоватьПуть(ОбъединитьПути(ТекущийСценарий().Каталог, "../..")); +УстановитьТекущийКаталог(ТекущийКаталог); + Поделка = Новый Поделка; Поделка.ЗапуститьПриложение(); \ 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 e04edc8..05ad5b5 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" @@ -207,7 +207,7 @@ // ЭтотОбъект - Для возможности цепочки вызовов // Функция УстановитьРасположениеКонтроллеров(Расположение) Экспорт - _РасположениеКонтроллеров = Расположение; + _РасположениеКонтроллеров = ФС.НормализоватьПуть(Расположение); Возврат ЭтотОбъект; КонецФункции 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" index cc54481..dc9807b 100644 --- "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" @@ -10,12 +10,14 @@ Для Каждого ПолноеИмя Из МассивФайлов Цикл - Если ФС.КаталогСуществует(ПолноеИмя) Тогда - ПодключитьИзКаталога(ПолноеИмя); - ИначеЕсли ФС.ФайлСуществует(ПолноеИмя) Тогда - Подключить(ПолноеИмя); + Путь = ФС.НормализоватьПуть(ПолноеИмя); + + Если ФС.КаталогСуществует(Путь) Тогда + ПодключитьИзКаталога(Путь); + ИначеЕсли ФС.ФайлСуществует(Путь) Тогда + Подключить(Путь); Иначе - ВызватьИсключение СтрШаблон("Не удалось подключить контроллер '%1': файл не существует", ПолноеИмя); + ВызватьИсключение СтрШаблон("Не удалось подключить контроллер '%1': файл или каталог не существует", ПолноеИмя); КонецЕсли; КонецЦикла; From 8bdc6a64a5be61b8dd28c05b63b674157f483137 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 16:37:02 +0300 Subject: [PATCH 11/14] =?UTF-8?q?docs:=20=D0=94=D0=BE=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=20=D1=80=D0=B0=D0=B7=D0=B4=D0=B5=D0=BB=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=BE=D0=BB=D0=BB=D0=B5?= =?UTF-8?q?=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eb71a8e..a1fe7e5 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ IP-адрес или имя хоста.
### Создание контроллера -Контроллер представляет собой класс OneScript с аннотацией `&Контроллер`, определяющей базовый путь маршрута. Более подробно можно прочитать в документации [WINOW](https://github.com/autumn-library/winow). +Контроллер представляет собой класс OneScript с аннотацией `&Контроллер`, определяющей базовый путь маршрута. Внутри контроллера создаются точки маршрута (эндпоинты) с помощью аннотации `&ТочкаМаршрута`. Более подробно можно прочитать в документации [WINOW](https://github.com/autumn-library/winow). **Пример контроллера** From 2b2a1c40638f8aa1058591c823646428837ab5f2 Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 18:38:53 +0300 Subject: [PATCH 12/14] =?UTF-8?q?docs:=20=D0=A3=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D0=B4=D0=BE=D0=BA=D1=83?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 +++++++++------- .../HttpBin.os" | 8 ++++---- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index a1fe7e5..ed97eb2 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Тестирование](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) -Локальный сервис для тестирования и отладки HTTP-запросов, реализованный на [OneScript](https://github.com/EvilBeaver/OneScript) с использованием библиотеки [WINOW](https://github.com/autumn-library/winow). +Локальный сервис для тестирования и отладки HTTP-запросов, реализованный на [OneScript](https://github.com/EvilBeaver/OneScript) с использованием фреймворка [WINOW](https://github.com/autumn-library/winow). Проект предоставляет функциональность, аналогичную [httpbin.org](https://httpbin.org/), поддерживая большинство оригинальных эндпоинтов. * 1\. [Установка](#installation) @@ -134,16 +134,18 @@ IP-адрес или имя хоста.
| `URL(<АдресРесурса>)` | Формирует полный URL-адрес сервиса с опциональным путем к ресурсу. | | `ТаймаутЗапуска()` | Возвращает текущее значение таймаута запуска сервиса. | | `УстановитьТаймаутЗапуска(<Таймаут>)` | Устанавливает максимальное время ожидания запуска сервиса. Применяется при синхронном запуске. | -| `РасположениеКонтроллеров()` | Возвращает текущий путь к папке или файлу с кастомными контроллерами. | -| `УстановитьРасположениеКонтроллеров(<Расположение>)` | Устанавливает путь к папке или файлу с кастомными контроллерами, определяющими маршруты сервиса. | +| `РасположениеКонтроллеров()` | Возвращает текущий путь к файлу или каталогу с кастомными контроллерами. | +| `УстановитьРасположениеКонтроллеров(<Расположение>)` | Устанавливает путь к файлу или каталогу с кастомными контроллерами, определяющими точки маршрута сервиса. | ## Кастомные эндпоинты -Сервис поддерживает подключение пользовательских контроллеров для расширения функциональности и добавления собственных эндпоинтов. +Сервис поддерживает подключение пользовательских контроллеров для добавления собственных эндпоинтов. ### Создание контроллера -Контроллер представляет собой класс OneScript с аннотацией `&Контроллер`, определяющей базовый путь маршрута. Внутри контроллера создаются точки маршрута (эндпоинты) с помощью аннотации `&ТочкаМаршрута`. Более подробно можно прочитать в документации [WINOW](https://github.com/autumn-library/winow). +Контроллер реализуется в виде класса OneScript с аннотацией `&Контроллер`. +В нём определяются точки маршрута (эндпоинты) с помощью аннотации `&ТочкаМаршрута`. +Подробнее см. в документации [WINOW](https://github.com/autumn-library/winow). **Пример контроллера** @@ -162,7 +164,7 @@ IP-адрес или имя хоста.
**Через программный интерфейс** -Для подключения кастомных контроллеров используйте метод `УстановитьРасположениеКонтроллеров()`, указав путь к папке или файлу с контроллерами: +Для подключения кастомных контроллеров используйте метод `УстановитьРасположениеКонтроллеров()`, указав путь к файлу или каталогу с контроллерами: ``` bsl HttpBin = Новый HttpBin() @@ -173,7 +175,7 @@ HttpBin = Новый HttpBin() **Через CLI** ``` bash -httpbin run --routes-handlers './path/to/routes' +httpbin run --routes-handlers './path/to/routes-handlers' ``` ## Ограничения 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 05ad5b5..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" @@ -9,7 +9,7 @@ Перем _ТаймаутПроверки; // Число - Таймаут HTTP-проверки доступности в секундах Перем _ИмяХоста; // Строка - IP-адрес или доменное имя хоста Перем _Порт; // Число - Номер TCP-порта сервиса -Перем _РасположениеКонтроллеров; // Строка, Неопределено - Путь к папке или файлу с кастомными контроллерами +Перем _РасположениеКонтроллеров; // Строка, Неопределено - Путь к файлу или каталогу с кастомными контроллерами Перем _Процесс; // Процесс, Неопределено - Объект запущенного процесса Перем _Лог; // Лог - Логгер для отладочной информации @@ -198,10 +198,10 @@ Возврат _ТаймаутЗапуска; КонецФункции -// Устанавливает путь к папке или файлу с кастомными контроллерами, определяющими маршруты сервиса. +// Устанавливает путь к файлу или каталогу с кастомными контроллерами, определяющими точки маршрута сервиса. // // Параметры: -// Расположение - Строка - Путь к папке или файлу с контроллерами (например, "./controllers" или "./МойКонтроллер.os") +// Расположение - Строка - Путь к файлу или каталогу с контроллерами // // Возвращаемое значение: // ЭтотОбъект - Для возможности цепочки вызовов @@ -211,7 +211,7 @@ Возврат ЭтотОбъект; КонецФункции -// Возвращает текущий путь к папке или файлу с кастомными контроллерами. +// Возвращает текущий путь к файлу или каталогу с кастомными контроллерами. // // Возвращаемое значение: // Строка, Неопределено From fc3b63329d6e18bd18507327ff6c8590e3d3efab Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 18:49:36 +0300 Subject: [PATCH 13/14] =?UTF-8?q?test:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82=D0=B5=D1=81=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=20=D1=81=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D0=BC=20=D1=82=D0=B5=D0=BA=D1=83=D1=89=D0=B8=D0=BC?= =?UTF-8?q?=20=D0=BA=D0=B0=D1=82=D0=B0=D0=BB=D0=BE=D0=B3=D0=BE=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/HttpBin_test.os | 75 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 15 deletions(-) diff --git a/tests/HttpBin_test.os b/tests/HttpBin_test.os index 151c36a..586e665 100644 --- a/tests/HttpBin_test.os +++ b/tests/HttpBin_test.os @@ -5,22 +5,31 @@ #Использовать 1connector #Использовать "../src/core" -Перем HttpBin; // HttpBin +Перем _HttpBin; // HttpBin +Перем _ТекущийКаталог; // Строка + +Процедура ПередЗапускомТеста() Экспорт + _ТекущийКаталог = ТекущийКаталог(); +КонецПроцедуры Процедура ПослеЗапускаТеста() Экспорт - Если Не HttpBin = Неопределено Тогда - HttpBin.Остановить(); + + УстановитьТекущийКаталог(_ТекущийКаталог); + + Если Не _HttpBin = Неопределено Тогда + _HttpBin.Остановить(); КонецЕсли; + КонецПроцедуры &Тест Процедура ТестДолжен_ЗапуститьСервисСинхронноИОстановить() Экспорт // Действие - HttpBin = Новый HttpBin().Запустить(); + _HttpBin = Новый HttpBin().Запустить(); // Проверка - Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры @@ -28,22 +37,22 @@ Процедура ТестДолжен_ЗапуститьСервисАсинхронноИОстановить() Экспорт // Подготовка - HttpBin = Новый HttpBin(); + _HttpBin = Новый HttpBin(); ВремяНачалаЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах(); - HttpBin.Запустить(); + _HttpBin.Запустить(); ВремяЗапуска = ТекущаяУниверсальнаяДатаВМиллисекундах() - ВремяНачалаЗапуска; - HttpBin.Остановить(); + _HttpBin.Остановить(); Приостановить(200); // Действие - HttpBin.ЗапуститьАсинх(); + _HttpBin.ЗапуститьАсинх(); Приостановить(ВремяЗапуска); // Проверка - Ожидаем.Что(HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); КонецПроцедуры @@ -51,12 +60,12 @@ Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзФайла() Экспорт // Подготовка - HttpBin = Новый HttpBin() + _HttpBin = Новый HttpBin() .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры/КастомныйКонтроллер.os") .Запустить(); // Действие - Ответ = КоннекторHTTP.Get(HttpBin.URL("/order/add"), , Новый Структура("Таймаут", 3)).Текст(); + Ответ = ВыполнитьЗапрос("/order/add").Текст(); // Проверка Ожидаем.Что(Ответ).Равно("success"); @@ -67,14 +76,50 @@ Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзКаталога() Экспорт // Подготовка - HttpBin = Новый HttpBin() + _HttpBin = Новый HttpBin() .УстановитьРасположениеКонтроллеров("./tests/fixtures/КастомныеКонтроллеры") .Запустить(); // Действие - Ответ = КоннекторHTTP.Get(HttpBin.URL("/order/add"), , Новый Структура("Таймаут", 3)).Текст(); + Ответ = ВыполнитьЗапрос("/order/add").Текст(); + + // Проверка + Ожидаем.Что(Ответ).Равно("success"); + +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисСКастомнымКонтроллеромИзКаталогаСИзмененнымТекущийКаталогом() Экспорт + + // Подготовка + УстановитьТекущийКаталог("tests"); + + _HttpBin = Новый HttpBin() + .УстановитьРасположениеКонтроллеров("./fixtures/КастомныеКонтроллеры") + .Запустить(); + + // Действие + Ответ = ВыполнитьЗапрос("/order/add").Текст(); // Проверка Ожидаем.Что(Ответ).Равно("success"); -КонецПроцедуры \ No newline at end of file +КонецПроцедуры + +&Тест +Процедура ТестДолжен_ЗапуститьСервисИзмененнымиТекущимКаталогом() Экспорт + + // Подготовка + УстановитьТекущийКаталог("tests"); + + // Действие + _HttpBin = Новый HttpBin().Запустить(); + + // Проверка + Ожидаем.Что(_HttpBin.Отвечает(), "Должен быть запущен").ЭтоИстина(); + +КонецПроцедуры + +Функция ВыполнитьЗапрос(АдресРесурса) + Возврат КоннекторHTTP.Get(_HttpBin.URL(АдресРесурса), , Новый Структура("Таймаут", 3)); +КонецФункции \ No newline at end of file From 9c2addfe87edfa360363462b35ca6f8e1556203e Mon Sep 17 00:00:00 2001 From: Dmitry Ivanov Date: Wed, 8 Oct 2025 19:07:21 +0300 Subject: [PATCH 14/14] =?UTF-8?q?docs:=20=D0=9F=D0=B5=D1=80=D0=B5=D0=B8?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD=20=D1=80=D0=B0=D0=B7?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed97eb2..a515fb0 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ * 2.3\. [Swagger UI](#swagger-ui) * 3\. [Совместимость](#compatibility) * 4\. [Программный интерфейс](#api) -* 5\. [Кастомные эндпоинты](#custom-endpoints) +* 5\. [Пользовательские эндпоинты](#custom-endpoints) * 6\. [Ограничения](#limitations) * 7\. [Сравнение с httpbin.org](#comparison) @@ -137,7 +137,7 @@ IP-адрес или имя хоста.
| `РасположениеКонтроллеров()` | Возвращает текущий путь к файлу или каталогу с кастомными контроллерами. | | `УстановитьРасположениеКонтроллеров(<Расположение>)` | Устанавливает путь к файлу или каталогу с кастомными контроллерами, определяющими точки маршрута сервиса. | -## Кастомные эндпоинты +## Пользовательские эндпоинты Сервис поддерживает подключение пользовательских контроллеров для добавления собственных эндпоинтов.