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