IO v7. Middleware и работа с ними
Описание
Middleware (промежуточное ПО) предоставляет удобный механизм для фильтрации HTTP-запросов, поступающих в приложение. Middleware выполняются до и после вызова экшена роута, что позволяет:
- Проверять авторизацию пользователя
- Добавлять CORS заголовки
- Логировать запросы
- Модифицировать входящие запросы или исходящие ответы
- Ограничивать доступ по IP
- И многое другое
Фреймворк также предоставляет встроенную поддержку CORS (Cross-Origin Resource Sharing) с автоматической обработкой preflight (OPTIONS) запросов.
Каждый middleware может:
- До выполнения (
handle): проверить запрос, модифицировать данные, прервать выполнение. - После выполнения (
terminate): модифицировать ответ, логировать, добавлять заголовки.
Middleware используются в Route.
Интерфейс Middleware
Все middleware должны реализовывать интерфейс IO\Middleware:
<?php
namespace IO;
interface Middleware
{
/**
* Обработка запроса до выполнения роута
*
* @param App $app Экземпляр приложения
* @param Route $route Экземпляр роута
* @param array $params Параметры middleware
* @return mixed|null Если возвращает не null, выполнение прерывается
*/
public function handle($app, $route, $params = []);
/**
* Обработка после выполнения роута (опционально)
*
* @param App $app Экземпляр приложения
* @param Route $route Экземпляр роута
* @param mixed $response Ответ роута
* @param array $params Параметры middleware
* @return void
*/
public function terminate($app, $route, $response, $params = []);
}
Хранение данных в маршруте
Middleware могут передавать данные между handle и terminate через контекст маршрута:
// В handle
$route->set('user_id', 123);
$route->set('data', ['key' => 'value']);
// В terminate
$userId = $route->get('user_id');
$data = $route->get('data');
Методы Route для работы с Middleware
addMiddleware()
Добавляет middleware в цепочку выполнения для текущего роута программно.
/**
* Добавить middleware в цепочку
*
* @param string|Middleware $middleware Класс middleware (например, \IO\Middleware\Auth::class) или объект
* @param array $params Параметры для middleware
* @return self Возвращает текущий объект роута для цепочечного вызова
*/
protected function addMiddleware($middleware, $params = [])
Параметры:
$middleware- полное имя класса middleware или объект, реализующий интерфейсMiddleware$params- ассоциативный массив параметров, которые будут переданы в методыhandle()иterminate()
Примеры:
<?php
namespace App\Routes;
use IO\Route;
class DashboardRoute extends Route
{
public function init()
{
parent::init();
// Простая авторизация
$this->addMiddleware(\IO\Middleware\Auth::class);
// Авторизация с параметрами
$this->addMiddleware(\IO\Middleware\Auth::class, [
'redirect' => '/login',
'json' => false
]);
// Несколько middleware
$this->addMiddleware(\IO\Middleware\Cors::class, [
'allow_origin' => ['https://example.com']
]);
$this->addMiddleware(\App\Middleware\CheckPermission::class, [
'permission' => 'dashboard.view',
'log' => true
]);
}
public function actionIndex()
{
return $this->render('@app/dashboard.twig');
}
}
Встроенные middleware
IO\Middleware\AuthMiddleware
Назначение: Проверка авторизации пользователя. Перенаправляет неавторизованных пользователей на страницу входа или возвращает JSON-ошибку для API.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
redirect | string | /login | URL для перенаправления неавторизованных пользователей |
json | bool | false | Возвращать JSON вместо редиректа для API запросов |
Данные в контексте маршрута
| Ключ | Тип | Описание | | user | array | Информация о пользователе | | user_id | int\|string | ID пользователя | | user_login | string | Логин пользователя | | permissions | array | Права доступа пользователя | | current_account | int | Текущий аккаунт | | current_cabinet | int | ID текущего кабинета |
Примеры использования:
// В маршруте
// Редирект на страницу логина
$app->add_route([
"url" => "/cabinet",
"name" => "app:cabinet",
"method" => "actionIndex",
"middleware" => [
\IO\Middleware\AuthMiddleware::class => [
'redirect' => '/login',
],
]
]);
// API с JSON ответом при ошибке
$app->add_route([
"url" => "/api/user/data",
"name" => "app:api:userdata",
"method" => "actionUserData",
"middleware" => [
\IO\Middleware\AuthMiddleware::class => [
'json' => true,
],
]
]);
// В роуте
public function actionDashboard()
{
$userId = $this->get('user_id');
$userInfo = $this->get('user');
return $this->json(['user' => $userInfo]);
}
IO\Middleware\CacheMiddleware
Назначение: Кэширование GET-запросов для уменьшения нагрузки на сервер.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
ttl | int | 300 | Время жизни кэша в секундах |
cacheByUser | bool | false | Кэшировать отдельно для каждого пользователя |
excludePaths | array | [] | Пути, которые не нужно кэшировать |
Пример использования:
$app->add_route([
'url' => '/api/catalog',
"name" => "app:api:catalog",
'method' => 'actionCatalog',
'middleware' => [
\IO\Middlewares\CacheMiddleware::class => [
'ttl' => 3600, // Кэш на 1 час
'cacheByUser' => false,
'excludePaths' => ['/api/catalog/search'],
],
]
]);
IO\Middleware\CompressionMiddleware
Назначение: Gzip-сжатие ответов для уменьшения трафика.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
level | int | 6 | Уровень сжатия (1-9, где 9 - максимальное) |
minSize | int | 1024 | Минимальный размер для сжатия в байтах |
contentTypes | array | [...] | Типы контента для сжатия |
Пример использования:
// Глобальное применение
$app->add_middleware([
\IO\Middlewares\CompressionMiddleware::class => [
'level' => 6,
'minSize' => 2048,
]
]);
IO\Middleware\CorsDebugMiddleware
Назначение: Отладка CORS-запросов (только для разработки). Логирует все CORS заголовки.
Параметры: Нет параметров.
Пример использования:
if (getenv('CLOUD_ENV') === 'dev')
{
$app->add_middleware([
\IO\Middlewares\CorsDebugMiddleware::class => [],
]);
}
IO\Middleware\CorsMiddleware
Назначение: Полноценная обработка CORS (Cross-Origin Resource Sharing) запросов.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
project | string | null | Название проекта для динамических origin |
extraOrigins | array | [] | Дополнительные разрешенные origin |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
cors_config | array | Конфигурация CORS |
Пример использования:
$app->add_route([
'url' => '/api/public',
"name" => "app:api:public",
'method' => 'actionPublic',
'middleware' => [
\IO\Middlewares\CorsMiddleware::class => [
'project' => 'myproject',
'extraOrigins' => ['https://example.com', 'https://app.example.com'],
],
]
]);
IO\Middleware\CsrfMiddleware
Назначение: Защита от CSRF (Cross-Site Request Forgery) атак.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
excludeMethods | array | ['GET', 'HEAD', 'OPTIONS'] | HTTP методы, для которых не проверять CSRF |
excludePaths | array | [] | Пути, для которых не проверять CSRF |
tokenName | string | 'csrf_token' | Имя CSRF токена |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
csrf_token | string | Новый CSRF токен для следующего запроса |
Пример использования:
$app->add_route([
'url' => '/api/user/update',
"name" => "app:api:user:update",
'method' => 'actionUpdate',
'middleware' => [
\IO\Middlewares\CsrfMiddleware::class => [
'excludeMethods' => ['GET', 'HEAD', 'OPTIONS'],
'tokenName' => 'csrf_token',
],
]
]);
IO\Middleware\DebugBarMiddleware
Назначение: Отладочная панель для разработки (только для локальных IP).
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
enabled | bool | false | Включена ли отладка |
allowedIps | array | ['127.0.0.1', '::1'] | Список IP, для которых показывать панель |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
debugbar_enabled | bool | Включена ли отладка |
debugbar_data | array | Данные для отладки |
Пример использования:
if (getenv('CLOUD_ENV') === 'dev')
{
$app->add_middleware([
\IO\Middlewares\DebugBarMiddleware::class => [
'enabled' => true,
'allowedIps' => ['127.0.0.1', '192.168.1.0/24'],
],
]);
}
IO\Middleware\IpWhitelistMiddleware
Назначение: Ограничение доступа по IP-адресам (белый и черный списки).
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
whitelist | array | [] | Разрешенные IP адреса (поддерживает CIDR) |
blacklist | array | [] | Запрещенные IP адреса (поддерживает CIDR) |
allowPrivate | bool | false | Разрешить приватные IP адреса |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
client_ip | string | IP адрес клиента |
Пример использования:
$app->add_route([
'url' => '/admin',
"name" => "app:admin",
'method' => 'actionAdmin',
'middleware' => [
\IO\Middlewares\IpWhitelistMiddleware::class => [
'whitelist' => ['10.0.0.0/8', '192.168.1.100'],
'blacklist' => ['1.2.3.4'],
'allowPrivate' => true,
],
]
]);
IO\Middleware\LoggingMiddleware
Назначение: Логирование всех HTTP-запросов.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
logFile | string | '/var/log/io7/requests.log' | Путь к файлу лога |
logHeaders | bool | true | Логировать заголовки |
logBody | bool | false | Логировать тело запроса |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
log_start_time | float | Время начала запроса |
Пример использования:
$app->add_middleware([
\IO\Middlewares\LoggingMiddleware::class => [
'logFile' => '/var/log/myapp/requests.log',
'logHeaders' => true,
'logBody' => true,
],
]);
IO\Middleware\MaintenanceMiddleware
Назначение: Режим обслуживания сайта.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
enabled | bool | false | Включен ли режим обслуживания |
allowedIPs | array | [] | IP адреса, для которых режим не применяется |
allowedUsers | array | [] | ID пользователей, для которых режим не применяется |
message | string | 'Сайт на техническом обслуживании...' | Сообщение для пользователей |
Пример использования:
// Включение через переменную окружения
$maintenance = getenv('MAINTENANCE_MODE') === 'true';
$app->add_middleware([
\IO\Middlewares\MaintenanceMiddleware::class => [
'enabled' => $maintenance,
'allowedIPs' => ['10.0.0.1', '192.168.1.100'],
'allowedUsers' => [1, 2, 3],
'message' => 'Проводим обновление. Вернемся через 15 минут.',
],
]);
// Обход режима через токен:
// https://site.com/?maintenance_token=секретный_токен
IO\Middleware\PerformanceMiddleware
Назначение: Мониторинг производительности запросов.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
slowThreshold | int | 1000 | Порог медленного запроса в миллисекундах |
logQueries | bool | true | Логировать медленные SQL запросы |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
perf_duration | float | Длительность запроса в мс |
perf_memory_used | int | Использовано памяти (байт) |
perf_memory_peak | int | Пиковое использование памяти |
is_slow_request | bool | Флаг медленного запроса |
Пример использования:
$app->add_middleware([
\IO\Middlewares\PerformanceMiddleware::class => [
'slowThreshold' => 500,
'logQueries' => true,
],
]);
// В роуте
public function actionSlow()
{
$duration = $this->get('perf_duration');
echo "Request took: {$duration}ms";
}
IO\Middleware\RateLimitMiddleware
Назначение: Ограничение частоты запросов по IP или пользователю.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
maxRequests | int | 60 | Максимальное количество запросов |
timeWindow | int | 60 | Временное окно в секундах |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
rate_limit_limit | int | Лимит запросов |
rate_limit_remaining | int | Осталось запросов |
rate_limit_reset | int | Время сброса (timestamp) |
Пример использования:
$app->add_route([
'url' => '/api/login',
"name" => "app:api:login",
'method' => 'actionLogin',
'middleware' => [
\IO\Middlewares\RateLimitMiddleware::class => [
'maxRequests' => 5, // 5 попыток
'timeWindow' => 300, // за 5 минут
],
],
]);
// Ответ при превышении:
// HTTP/1.1 429 Too Many Requests
// X-RateLimit-Limit: 5
// X-RateLimit-Remaining: 0
// X-RateLimit-Reset: 1700000000
IO\Middleware\RequestIdMiddleware
Назначение: Добавление уникального ID к каждому запросу.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
headerName | string | 'X-Request-Id' | Название заголовка для ID запроса |
generateIfMissing | bool | true | Генерировать ID если отсутствует |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
request_id | string | Уникальный ID запроса |
Пример использования:
$app->add_middleware([
\IO\Middlewares\RequestIdMiddleware::class => [
'headerName' => 'X-Request-Id',
'generateIfMissing' => true,
],
]);
// Использование в коде
$requestId = $this->get('request_id');
error_log("Processing request: $requestId");
IO\Middleware\SanitizeMiddleware
Назначение: Санитизация (очистка) входных данных.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
sanitizeGet | bool | true | Санитизировать GET параметры |
sanitizePost | bool | true | Санитизировать POST данные |
sanitizeJson | bool | true | Санитизировать JSON данные |
Пример использования:
$app->add_middleware([
\IO\Middlewares\SanitizeMiddleware::class => [
'sanitizeGet' => true,
'sanitizePost' => true,
'sanitizeJson' => true,
],
]);
// После санитизации:
// - Удаляются HTML теги
// - Экранируются спецсимволы
// - Удаляются пробелы в начале и конце
IO\Middleware\SecurityHeadersMiddleware
Назначение: Установка заголовков безопасности.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
x_frame_options | string | 'DENY' | Защита от clickjacking |
x_content_type_options | string | 'nosniff' | Защита от MIME-типов |
x_xss_protection | string | '1; mode=block' | Защита от XSS |
referrer_policy | string | 'strict-origin-when-cross-origin' | Политика referrer |
permissions_policy | string | 'geolocation=(), microphone=(), camera=()' | Политика разрешений |
hsts_enabled | bool | true | Включить HSTS |
hsts_max_age | int | 31536000 | Время жизни HSTS |
content_security_policy | string | "default-src 'self'" | CSP политика |
Пример использования:
$app->add_middleware([
\IO\Middlewares\SecurityHeadersMiddleware::class => [
'x_frame_options' => 'SAMEORIGIN',
'content_security_policy' => "default-src 'self' https:; script-src 'self' 'unsafe-inline'",
],
]);
IO\Middleware\ThrottleMiddleware
Назначение: Троттлинг (ограничение) запросов с разными лимитами для разных типов.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
limits | array | [...] | Конфигурация лимитов |
Структура лимитов:
$limits = [
'default' => ['requests' => 100, 'window' => 3600], // 100 запросов в час
'auth' => ['requests' => 20, 'window' => 60], // 20 запросов в минуту
'api' => ['requests' => 500, 'window' => 3600], // 500 запросов в час
];
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
throttle_limit | int | Лимит запросов |
throttle_remaining | int | Осталось запросов |
throttle_reset | int | Время сброса |
Пример использования:
$app->add_middleware([
\IO\Middlewares\ThrottleMiddleware::class => [
'limits' => [
'default' => ['requests' => 50, 'window' => 3600],
'auth' => ['requests' => 100, 'window' => 3600],
'api' => ['requests' => 1000, 'window' => 3600],
],
],
]);
IO\Middleware\ValidationMiddleware
Назначение: Валидация входных данных.
Параметры:
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
rules | array | [] | Правила валидации |
Правила валидации:
| Правило | Описание | Пример |
|---|---|---|
required | Поле обязательно | 'required' => true |
min | Минимальная длина | 'min' => 3 |
max | Максимальная длина | 'max' => 255 |
email | Проверка email | 'email' => true |
numeric | Проверка числа | 'numeric' => true |
integer | Проверка целого числа | 'integer' => true |
boolean | Проверка булева значения | 'boolean' => true |
regex | Регулярное выражение | 'regex' => '/^[A-Z]+$/' |
in | Список допустимых значений | 'in' => ['admin', 'user'] |
url | Проверка URL | 'url' => true |
date | Проверка даты (Y-m-d) | 'date' => true |
datetime | Проверка даты-времени | 'datetime' => true |
Данные в контексте маршрута:
| Ключ | Тип | Описание |
|---|---|---|
validated_data | array | Валидированные данные |
original_data | array | Исходные данные |
Пример использования:
$app->add_route([
'url' => '/api/user/create',
"name" => "app:api:user:create",
'method' => 'actionCreate',
'middleware' => [
\IO\Middlewares\ValidationMiddleware::class => [
'rules' => [
'name' => ['required' => true, 'min' => 2, 'max' => 50],
'email' => ['required' => true, 'email' => true],
'age' => ['numeric' => true, 'min' => 18, 'max' => 120],
'role' => ['in' => ['admin', 'user', 'guest']],
'website' => ['url' => true],
'birth_date' => ['date' => true],
'_extra' => false, // Запрещает дополнительные поля
],
],
],
]);
// В роуте
public function actionCreate()
{
$validated = $this->get('validated_data');
$name = $validated['name'];
$email = $validated['email'];
// Создание пользователя...
}
Поддержка CORS
Фреймворк предоставляет встроенную поддержку CORS (Cross-Origin Resource Sharing) без необходимости использования middleware.
Конфигурация CORS в роуте
Для включения CORS добавьте ключ cors в конфигурацию роута:
$app->add_route([
"url" => "/api/data",
"name" => "app:api:data",
"method" => "actionData",
"cors" => [
'allow_origin' => ['https://client.com', 'https://app.com'],
'allow_credentials' => true,
'allow_methods' => ['GET', 'POST'],
'allow_headers' => ['Content-Type', 'Authorization'],
'max_age' => 3600,
]
]);
Параметры CORS
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
allow_origin | array | [] | Список разрешенных источников (доменов) |
allow_methods | array | ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'] | Разрешенные HTTP методы |
allow_headers | array | ['Content-Type', 'Authorization', 'X-Requested-With', 'Accept'] | Разрешенные заголовки |
allow_credentials | bool | false | Разрешить отправку кук и HTTP-авторизацию |
max_age | int | 86400 | Время кеширования preflight запроса (в секундах) |
expose_headers | array | [] | Заголовки, доступные для чтения в браузере |
Автоматическая обработка OPTIONS
Фреймворк автоматически обрабатывает OPTIONS (preflight) запросы для роутов с конфигурацией CORS:
- При получении OPTIONS запроса, фреймворк находит соответствующий роут по URL
- Устанавливает CORS заголовки из конфигурации
- Возвращает ответ 200 без вызова экшена роута
- Для не-OPTIONS запросов CORS заголовки устанавливаются перед выполнением экшена
Утилита CorsTrait
Для удобной генерации конфигурации CORS на основе глобальной переменной $ioProjects предоставляется трейт IO\CorsTrait:
<?php
namespace App\Routes;
use IO\Route;
use IO\CorsTrait;
class ApiRoute extends Route
{
use CorsTrait;
public static function routes($app)
{
$instance = new self();
// Автоматическая генерация origins из $ioProjects
$corsConfig = $instance->getCorsConfig('mobi');
$app->add_route([
"url" => "/api/user/data",
"name" => "app:api:userdata",
"method" => "actionUserData",
"cors" => $corsConfig,
"middleware" => [
\IO\Middleware\AuthMiddleware::class => ['json' => true]
]
]);
}
}
Метод getCorsConfig()
/**
* Получить конфигурацию CORS для проекта
*
* @param string|null $project Имя проекта (например, 'mobi', 'admin')
* @param array $extraOrigins Дополнительные источники для добавления
* @return array Конфигурация CORS
*/
protected function getCorsConfig($project = null, $extraOrigins = [])
Автоматически включает:
- Все проекты из $ioProjects (с https и http для dev-среды)
- Специфичный домен для указанного проекта
- Дополнительные источники из параметра $extraOrigins
Создание собственного middleware
Пример: Middleware для логирования
<?php
namespace App\Middleware;
use IO\Middleware;
class LoggerMiddleware implements Middleware
{
protected $logFile = '/var/log/requests.log';
public function handle($app, $route, $params = [])
{
if (isset($params['log_file']))
{
$this->logFile = $params['log_file'];
}
$this->log('START ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI']);
return null;
}
public function terminate($app, $route, $response, $params = [])
{
$this->log('END ' . $_SERVER['REQUEST_METHOD'] . ' ' . $_SERVER['REQUEST_URI'] .
' - ' . http_response_code());
}
protected function log($message)
{
$date = date('Y-m-d H:i:s');
$ip = getRealIP();
file_put_contents($this->logFile, "[$date] [$ip] $message\n", FILE_APPEND);
}
}
Пример: Middleware для проверки IP
<?php
namespace App\Middleware;
use IO\Middleware;
class IpFilterMiddleware implements Middleware
{
public function handle($app, $route, $params = [])
{
$ip = getRealIP();
$whitelist = $params['whitelist'] ?? [];
$blacklist = $params['blacklist'] ?? [];
$redirect = $params['redirect'] ?? '/403';
$json = $params['json'] ?? false;
// Проверка черного списка
if (in_array($ip, $blacklist))
{
return $this->deny('IP is blacklisted', $redirect, $json);
}
// Если есть белый список, проверяем вхождение
if (!empty($whitelist) && !in_array($ip, $whitelist))
{
return $this->deny('IP not in whitelist', $redirect, $json);
}
return null;
}
protected function deny($message, $redirect, $json)
{
if ($json)
{
return [
'error_code' => -12,
'error_str' => $message
];
}
header("Location: $redirect");
exit;
}
public function terminate($app, $route, $response, $params = []) {}
}
Использование кастомных middleware
<?php
namespace App\Routes;
use IO\Route;
class AdminRoute extends Route
{
public static function routes($app)
{
$app->add_route([
"url" => "/admin",
"name" => "app:admin",
"method" => "actionIndex",
"middleware" => [
// Проверка IP
\App\Middleware\IpFilter::class => [
'whitelist' => ['192.168.1.0/24', '10.0.0.1'],
'redirect' => '/403'
],
// Авторизация
\IO\Middleware\AuthMiddleware::class => [
'redirect' => '/login'
],
// Логирование
\App\Middleware\Logger::class => [
'log_file' => '/var/log/admin.log'
]
]
]);
}
}
Порядок выполнения
Middleware выполняются в следующем порядке:
- Middleware из конфигурации роута (в порядке объявления)
- Middleware, добавленные через
addMiddleware()(в порядке добавления)
Для каждого middleware:
- Сначала вызывается метод
handle()(этап before) - Выполняется экшен роута
- Затем для каждого middleware (в обратном порядке) вызывается метод
terminate()(этап after)
$this->addMiddleware(MiddlewareA::class);
$this->addMiddleware(MiddlewareB::class);
$this->addMiddleware(MiddlewareC::class);
// Порядок выполнения:
// 1. MiddlewareA::handle()
// 2. MiddlewareB::handle()
// 3. MiddlewareC::handle()
// 4. Выполнение экшена роута
// 5. MiddlewareC::terminate()
// 6. MiddlewareB::terminate()
// 7. MiddlewareA::terminate()
Если какой-либо middleware возвращает не-null значение в handle(), цепочка прерывается и ответ возвращается немедленно.
Примеры использования
Пример 1: Защищенное API с CORS
<?php
namespace App\Routes;
use IO\Route;
use IO\CorsTrait;
class ApiRoute extends Route
{
use CorsTrait;
public static function routes($app)
{
$instance = new self();
// Публичное API (без авторизации)
$app->add_route([
"url" => "/api/version",
"name" => "app:api:version",
"method" => "actionVersion",
"cors" => [
'allow_origin' => ['*'],
'allow_methods' => ['GET'],
],
]);
// Защищенное API
$app->add_route([
"url" => "/api/user/data",
"name" => "app:api:userdata",
"method" => "actionUserData",
"cors" => $instance->getCorsConfig('mobi'),
"middleware" => [
\IO\Middleware\AuthMiddleware::class => ['json' => true],
],
]);
}
public function actionVersion()
{
return $this->json(['version' => '1.0.0']);
}
public function actionUserData()
{
global $ioSession;
return $this->json([
'user' => $ioSession->getUserInfo()
]);
}
}
Пример 2: Административная панель с несколькими middleware
<?php
namespace App\Routes;
use IO\Route;
class AdminRoute extends Route
{
public function init()
{
parent::init();
// Добавляем проверку авторизации для всех экшенов
$this->addMiddleware(\IO\Middleware\AuthMiddleware::class, [
'redirect' => '/admin/login',
]);
// Добавляем проверку IP
$this->addMiddleware(\App\Middleware\IpFilterMiddleware::class, [
'whitelist' => ['10.0.0.0/8', '192.168.1.0/24'],
'redirect' => '/403',
]);
}
public function actionIndex()
{
return $this->render('@app/admin/index.twig');
}
public function actionUsers()
{
return $this->render('@app/admin/users.twig');
}
}
Пример 3: Внутренний сервис (без CORS)
// Внутренний сервис, вызывается только через /gate/
$app->add_route([
"url" => "/internal/process",
"name" => "app:internal:process",
"method" => "actionProcess",
"type" => ["post"],
"nosession" => true,
// cors отсутствует - CORS заголовки не добавляются
]);
Пример 4: Динамическое добавление middleware
<?php
namespace App\Routes;
use IO\Route;
class DynamicRoute extends Route
{
public function actionConditional()
{
// Добавляем middleware только при определенном условии
if ($this->input('debug') === 'true')
{
$this->addMiddleware(\App\Middleware\DebugLoggerMiddleware::class);
}
// ... остальной код
}
}
Глобальное подключение middleware
// В App/Module.php
public static function init($app)
{
// Глобальные middleware для всех маршрутов
$app->add_middleware([
\IO\Middlewares\RequestIdMiddleware::class => [],
\IO\Middlewares\SecurityHeadersMiddleware::class => [],
\IO\Middlewares\PerformanceMiddleware::class => ['slowThreshold' => 500],
]);
// Регистрация маршрутов
$app->add_routes([
\IO\Routes\ApiRoute::class,
\App\Routes\CustomRoute::class,
]);
}
Группировка middleware для маршрутов
// В Routes/ApiRoute.php
public static function routes($app)
{
// Публичные API (без авторизации)
$app->add_route([
'url' => '/api/public/login',
"name" => "app:api:public:login",
'method' => 'actionLogin',
'middleware' => [
\IO\Middlewares\RateLimitMiddleware::class => ['maxRequests' => 5, 'timeWindow' => 300],
\IO\Middlewares\ValidationMiddleware::class => [
'rules' => [
'login' => ['required' => true],
'password' => ['required' => true, 'min' => 6],
],
],
],
]);
// Защищенные API (с авторизацией)
$app->add_route([
'url' => '/api/users',
"name" => "app:api:users",
'method' => 'actionUsers',
'middleware' => [
\IO\Middlewares\AuthMiddleware::class => ['redirect' => '/login', 'json' => true],
\IO\Middlewares\ThrottleMiddleware::class => [],
\IO\Middlewares\CacheMiddleware::class => ['ttl' => 300],
],
]);
}
Комбинирование нескольких middleware
$app->add_route([
'url' => '/admin/users',
"name" => "app:admin:users",
'method' => 'actionAdminUsers',
'middleware' => [
// Порядок важен! Выполняются сверху вниз
\IO\Middlewares\IpWhitelistMiddleware::class => [
'whitelist' => ['10.0.0.0/8', '192.168.1.0/24'],
],
\IO\Middlewares\AuthMiddleware::class => [
'redirect' => '/admin/login',
],
\IO\Middlewares\RateLimitMiddleware::class => [
'maxRequests' => 30,
'timeWindow' => 60,
],
\IO\Middlewares\ValidationMiddleware::class => [
'rules' => [
'page' => ['numeric' => true, 'min' => 1],
'limit' => ['numeric' => true, 'min' => 1, 'max' => 100],
],
],
],
]);
Проверка подписки пользователя
<?php
namespace App\Middlewares;
use IO\Middleware;
/**
* Проверка подписки пользователя
*/
class SubscriptionMiddleware implements Middleware
{
public function handle($app, $route, $params = [])
{
$userId = $route->get('user_id');
if (!$userId)
{
return [
'error_code' => 401,
'error_str' => 'User not found'
];
}
// Проверка подписки
$subscription = $this->checkSubscription($userId);
if (!$subscription || $subscription['expires'] < time())
{
return [
'error_code' => 402,
'error_str' => 'Subscription required'
];
}
// Сохраняем данные подписки
$route->set('subscription', $subscription);
return null;
}
public function terminate($app, $route, $response, $params = [])
{
// Логируем использование API
if ($route->has('subscription'))
{
error_log("API called by user with subscription");
}
}
private function checkSubscription($userId)
{
// Логика проверки подписки
return ['plan' => 'premium', 'expires' => strtotime('+1 month')];
}
}
Условное применение middleware
// Применяем middleware только в определенных условиях
$middleware = [];
if (getenv('CLOUD_ENV') === 'production')
{
$middleware[\IO\Middlewares\SecurityHeadersMiddleware::class] = [];
$middleware[\IO\Middlewares\RateLimitMiddleware::class] = ['maxRequests' => 60];
}
if (getenv('CLOUD_ENV') === 'dev')
{
$middleware[\IO\Middlewares\DebugBarMiddleware::class] = ['enabled' => true];
$middleware[\IO\Middlewares\CorsDebugMiddleware::class] = [];
}
$app->add_middleware($middleware);
Рекомендации по безопасности
- CORS только там, где нужно - не добавляйте CORS к внутренним сервисам.
- Явно указывайте origins - избегайте
*если используются credentials. - Ограничивайте методы - разрешайте только необходимые HTTP методы.
- Кешируйте preflight - используйте
max_ageдля уменьшения нагрузки. - Проверяйте middleware - всегда проверяйте входные данные.
Заключение
Middleware предоставляют мощный и гибкий способ обработки запросов. Используйте их для:
- Безопасности: AuthMiddleware, CsrfMiddleware, SecurityHeadersMiddleware.
- Производительности: CacheMiddleware, CompressionMiddleware, PerformanceMiddleware.
- Ограничений: RateLimitMiddleware, ThrottleMiddleware, IpWhitelistMiddleware.
- Отладки: DebugBarMiddleware, CorsDebugMiddleware, LoggingMiddleware.
- Качества данных: ValidationMiddleware, SanitizeMiddleware.
Помните о порядке выполнения middleware: они обрабатываются в том порядке, в котором добавлены. Данные между handle и terminate передаются через контекст маршрута с помощью методов set() и get().