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