BMC IO DocsBMC IO Docs
IO v6
IO v7
Notes
Docs
IO v6
IO v7
Notes
Docs
  • IO Framework v7

    • О фреймворке
    • Что изменилось в 7 версии
    • Структура
    • Доступ к участкам проекта
    • Разворачивание нового проекта
  • Backend

    • Настройки проекта Conf.php
    • Настройки модуля Module.php
    • Настройки composer
    • Функции фреймворка
    • Глобальные переменные
    • Константы фреймворка
    • Event класс
    • Core класс
    • Route и работа с ними
    • Request и работа с ними
    • Middleware и работа с ними
    • Class и работа с ними
    • Api model и работа с ними
    • Template и работа с ними
    • Мультиязычность и словарь фраз
    • Bin скрипты и работа с ними
    • Cron скрипты и работа с ними
    • $ioSession и работа с сессией
    • $ioHash и работа с кешем
    • $ioXCache и работа с кешем
    • $iodb и работа с базой данных
  • Frontend

    • Twig шаблонизатор и работа с ним
    • Twig функции
    • Twig фильтры
    • Twig контекст
    • Настройка NPM
    • Сборка Frontend составляющей
    • Обращение к Api через JS
    • Хранилище (store)
    • Навигация в хосте
    • Навигация в микрофронтенде
    • Параметры микрофронтенда
    • Проверка полномочий

IO v7. Middleware и работа с ними

  • Описание
  • Интерфейс Middleware
  • Хранение данных в маршруте
  • Методы Route для работы с Middleware
    • addMiddleware()
  • Встроенные middleware
    • IO\Middleware\AuthMiddleware
    • IO\Middleware\CacheMiddleware
    • IO\Middleware\CompressionMiddleware
    • IO\Middleware\CorsDebugMiddleware
    • IO\Middleware\CorsMiddleware
    • IO\Middleware\CsrfMiddleware
    • IO\Middleware\DebugBarMiddleware
    • IO\Middleware\IpWhitelistMiddleware
    • IO\Middleware\LoggingMiddleware
    • IO\Middleware\MaintenanceMiddleware
    • IO\Middleware\PerformanceMiddleware
    • IO\Middleware\RateLimitMiddleware
    • IO\Middleware\RequestIdMiddleware
    • IO\Middleware\SanitizeMiddleware
    • IO\Middleware\SecurityHeadersMiddleware
    • IO\Middleware\ThrottleMiddleware
    • IO\Middleware\ValidationMiddleware
  • Поддержка CORS
    • Конфигурация CORS в роуте
    • Параметры CORS
    • Автоматическая обработка OPTIONS
    • Утилита CorsTrait
  • Создание собственного middleware
    • Пример: Middleware для логирования
    • Пример: Middleware для проверки IP
    • Использование кастомных 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.

Параметры:

ПараметрТипПо умолчаниюОписание
redirectstring/loginURL для перенаправления неавторизованных пользователей
jsonboolfalseВозвращать 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-запросов для уменьшения нагрузки на сервер.

Параметры:

ПараметрТипПо умолчаниюОписание
ttlint300Время жизни кэша в секундах
cacheByUserboolfalseКэшировать отдельно для каждого пользователя
excludePathsarray[]Пути, которые не нужно кэшировать

Пример использования:

$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-сжатие ответов для уменьшения трафика.

Параметры:

ПараметрТипПо умолчаниюОписание
levelint6Уровень сжатия (1-9, где 9 - максимальное)
minSizeint1024Минимальный размер для сжатия в байтах
contentTypesarray[...]Типы контента для сжатия

Пример использования:

// Глобальное применение
$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) запросов.

Параметры:

ПараметрТипПо умолчаниюОписание
projectstringnullНазвание проекта для динамических origin
extraOriginsarray[]Дополнительные разрешенные origin

Данные в контексте маршрута:

КлючТипОписание
cors_configarrayКонфигурация 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) атак.

Параметры:

ПараметрТипПо умолчаниюОписание
excludeMethodsarray['GET', 'HEAD', 'OPTIONS']HTTP методы, для которых не проверять CSRF
excludePathsarray[]Пути, для которых не проверять CSRF
tokenNamestring'csrf_token'Имя CSRF токена

Данные в контексте маршрута:

КлючТипОписание
csrf_tokenstringНовый 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).

Параметры:

ПараметрТипПо умолчаниюОписание
enabledboolfalseВключена ли отладка
allowedIpsarray['127.0.0.1', '::1']Список IP, для которых показывать панель

Данные в контексте маршрута:

КлючТипОписание
debugbar_enabledboolВключена ли отладка
debugbar_dataarrayДанные для отладки

Пример использования:

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-адресам (белый и черный списки).

Параметры:

ПараметрТипПо умолчаниюОписание
whitelistarray[]Разрешенные IP адреса (поддерживает CIDR)
blacklistarray[]Запрещенные IP адреса (поддерживает CIDR)
allowPrivateboolfalseРазрешить приватные IP адреса

Данные в контексте маршрута:

КлючТипОписание
client_ipstringIP адрес клиента

Пример использования:

$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-запросов.

Параметры:

ПараметрТипПо умолчаниюОписание
logFilestring'/var/log/io7/requests.log'Путь к файлу лога
logHeadersbooltrueЛогировать заголовки
logBodyboolfalseЛогировать тело запроса

Данные в контексте маршрута:

КлючТипОписание
log_start_timefloatВремя начала запроса

Пример использования:

$app->add_middleware([
    \IO\Middlewares\LoggingMiddleware::class => [
        'logFile' => '/var/log/myapp/requests.log',
        'logHeaders' => true,
        'logBody' => true,
    ],
]);

IO\Middleware\MaintenanceMiddleware

Назначение: Режим обслуживания сайта.

Параметры:

ПараметрТипПо умолчаниюОписание
enabledboolfalseВключен ли режим обслуживания
allowedIPsarray[]IP адреса, для которых режим не применяется
allowedUsersarray[]ID пользователей, для которых режим не применяется
messagestring'Сайт на техническом обслуживании...'Сообщение для пользователей

Пример использования:

// Включение через переменную окружения
$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

Назначение: Мониторинг производительности запросов.

Параметры:

ПараметрТипПо умолчаниюОписание
slowThresholdint1000Порог медленного запроса в миллисекундах
logQueriesbooltrueЛогировать медленные SQL запросы

Данные в контексте маршрута:

КлючТипОписание
perf_durationfloatДлительность запроса в мс
perf_memory_usedintИспользовано памяти (байт)
perf_memory_peakintПиковое использование памяти
is_slow_requestboolФлаг медленного запроса

Пример использования:

$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 или пользователю.

Параметры:

ПараметрТипПо умолчаниюОписание
maxRequestsint60Максимальное количество запросов
timeWindowint60Временное окно в секундах

Данные в контексте маршрута:

КлючТипОписание
rate_limit_limitintЛимит запросов
rate_limit_remainingintОсталось запросов
rate_limit_resetintВремя сброса (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 к каждому запросу.

Параметры:

ПараметрТипПо умолчаниюОписание
headerNamestring'X-Request-Id'Название заголовка для ID запроса
generateIfMissingbooltrueГенерировать ID если отсутствует

Данные в контексте маршрута:

КлючТипОписание
request_idstringУникальный 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

Назначение: Санитизация (очистка) входных данных.

Параметры:

ПараметрТипПо умолчаниюОписание
sanitizeGetbooltrueСанитизировать GET параметры
sanitizePostbooltrueСанитизировать POST данные
sanitizeJsonbooltrueСанитизировать JSON данные

Пример использования:

$app->add_middleware([
    \IO\Middlewares\SanitizeMiddleware::class => [
        'sanitizeGet' => true,
        'sanitizePost' => true,
        'sanitizeJson' => true,
    ],
]);

// После санитизации:
// - Удаляются HTML теги
// - Экранируются спецсимволы
// - Удаляются пробелы в начале и конце

IO\Middleware\SecurityHeadersMiddleware

Назначение: Установка заголовков безопасности.

Параметры:

ПараметрТипПо умолчаниюОписание
x_frame_optionsstring'DENY'Защита от clickjacking
x_content_type_optionsstring'nosniff'Защита от MIME-типов
x_xss_protectionstring'1; mode=block'Защита от XSS
referrer_policystring'strict-origin-when-cross-origin'Политика referrer
permissions_policystring'geolocation=(), microphone=(), camera=()'Политика разрешений
hsts_enabledbooltrueВключить HSTS
hsts_max_ageint31536000Время жизни HSTS
content_security_policystring"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

Назначение: Троттлинг (ограничение) запросов с разными лимитами для разных типов.

Параметры:

ПараметрТипПо умолчаниюОписание
limitsarray[...]Конфигурация лимитов

Структура лимитов:

$limits = [
    'default' => ['requests' => 100, 'window' => 3600], // 100 запросов в час
    'auth'    => ['requests' => 20,  'window' => 60],   // 20 запросов в минуту
    'api'     => ['requests' => 500, 'window' => 3600], // 500 запросов в час
];

Данные в контексте маршрута:

КлючТипОписание
throttle_limitintЛимит запросов
throttle_remainingintОсталось запросов
throttle_resetintВремя сброса

Пример использования:

$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

Назначение: Валидация входных данных.

Параметры:

ПараметрТипПо умолчаниюОписание
rulesarray[]Правила валидации

Правила валидации:

ПравилоОписаниеПример
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_dataarrayВалидированные данные
original_dataarrayИсходные данные

Пример использования:

$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_originarray[]Список разрешенных источников (доменов)
allow_methodsarray['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']Разрешенные HTTP методы
allow_headersarray['Content-Type', 'Authorization', 'X-Requested-With', 'Accept']Разрешенные заголовки
allow_credentialsboolfalseРазрешить отправку кук и HTTP-авторизацию
max_ageint86400Время кеширования preflight запроса (в секундах)
expose_headersarray[]Заголовки, доступные для чтения в браузере

Автоматическая обработка 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 для логирования

app/Middlewares/LoggerMiddleware.php
<?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

app/Middlewares/IpFilterMiddleware.php
<?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 выполняются в следующем порядке:

  1. Middleware из конфигурации роута (в порядке объявления)
  2. Middleware, добавленные через addMiddleware() (в порядке добавления)

Для каждого middleware:

  1. Сначала вызывается метод handle() (этап before)
  2. Выполняется экшен роута
  3. Затем для каждого 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);

Рекомендации по безопасности

  1. CORS только там, где нужно - не добавляйте CORS к внутренним сервисам.
  2. Явно указывайте origins - избегайте * если используются credentials.
  3. Ограничивайте методы - разрешайте только необходимые HTTP методы.
  4. Кешируйте preflight - используйте max_age для уменьшения нагрузки.
  5. Проверяйте middleware - всегда проверяйте входные данные.

Заключение

Middleware предоставляют мощный и гибкий способ обработки запросов. Используйте их для:

  • Безопасности: AuthMiddleware, CsrfMiddleware, SecurityHeadersMiddleware.
  • Производительности: CacheMiddleware, CompressionMiddleware, PerformanceMiddleware.
  • Ограничений: RateLimitMiddleware, ThrottleMiddleware, IpWhitelistMiddleware.
  • Отладки: DebugBarMiddleware, CorsDebugMiddleware, LoggingMiddleware.
  • Качества данных: ValidationMiddleware, SanitizeMiddleware.

Помните о порядке выполнения middleware: они обрабатываются в том порядке, в котором добавлены. Данные между handle и terminate передаются через контекст маршрута с помощью методов set() и get().

Prev
Request и работа с ними
Next
Class и работа с ними