Skip to main content

10. Middlewares

Descripción General

El sistema de middlewares implementa el patrón Chain of Responsibility, permitiendo procesar solicitudes HTTP antes y después de llegar al controlador principal. Los middlewares ofrecen una forma modular de añadir funcionalidades como autenticación, validación, limitación de tasas y más, sin modificar el código base.

Arquitectura

El sistema de middlewares se basa en una interfaz común Middleware que define un método único handle(). Cada middleware recibe la solicitud actual y un callable que representa el siguiente middleware en la cadena. El kernel de la aplicación gestiona esta cadena, ejecutando los middlewares en un orden específico.

Componentes Principales:

  1. Interfaz Middleware: Define el contrato para todos los middlewares
  2. QuKernel: Gestiona el registro y ejecución de la cadena de middlewares
  3. Implementaciones de Middleware: Clases concretas que implementan la funcionalidad específica

Interfaces

Públicas

// Interfaz Middleware (App/Core/Interfaces/Middleware.php)
namespace App\Core\Interfaces;

use App\Core\RequestFactory as Request;

interface Middleware
{
public function handle(Request $request, callable $next);
}

Implementaciones Principales

El sistema incluye varios middlewares predefinidos:

  • TransactionStartMiddleware: Inicia el seguimiento de una transacción
  • TransactionEndMiddleware: Finaliza y registra la transacción
  • AuthMiddleware: Verifica la autenticación de usuarios
  • RateLimitMiddleware: Limita la cantidad de peticiones por tiempo
  • SwitchUtf8Latin1Middleware: Gestiona la codificación de caracteres
  • ValidatorMiddleware: Valida datos de entrada según reglas específicas

Flujo de Datos

  1. La solicitud HTTP llega al kernel de la aplicación
  2. El kernel construye la cadena de middlewares según la configuración
  3. El primer middleware recibe la solicitud y el resto de la cadena como un callable
  4. Cada middleware puede:
    • Modificar la solicitud antes de pasar al siguiente middleware
    • Ejecutar lógica después de recibir la respuesta del siguiente middleware
    • Interrumpir el flujo lanzando excepciones
  5. La respuesta final atraviesa la cadena en sentido inverso

Uso del Sistema de Middlewares

Registro de Middlewares Globales

Los middlewares globales se registran en el kernel durante la inicialización de la aplicación:

// index.php
$kernel = new QuKernel(new QuRouter, $container);

// Registramos middlewares globales
$kernel->addMiddleware(new TransactionStartMiddleware);

// Middlewares condicionales según la ruta
if (!in_array($pathInfo, $noAuth)) {
$kernel->addMiddleware(new Auth);
$kernel->addMiddleware(new RateLimitMiddleware);
$kernel->addMiddleware(new SwitchUtf8Latin1Middleware);
}

$kernel->addMiddleware(new TransactionEndMiddleware);

Middleware por Rutas

Los middlewares también pueden aplicarse a rutas específicas:

// ProductsRoutes.php
$middlewares = ['before' => [AuthMiddleware::class]];
Route::group(ProductsController::class, function () use ($middlewares) {
Route::get('products/', 'index', $middlewares);
Route::get('products/:id', 'show', $middlewares);
Route::post('products/', 'store', $middlewares);
Route::put('products/:id', 'update', $middlewares);
Route::delete('products/:id', 'delete', $middlewares);
});

Middlewares Específicos

10.1. Pipeline de Middleware

La clase QuKernel implementa el pipeline de middleware:

// App/Core/QuKernel.php
public function handle(Request $request)
{
// Construcción del handler (pipeline completo)
$handler = function ($request) use ($resolved) {
// Lógica final que invoca al controlador
// ...
};

// Construcción del pipeline (en orden inverso)
foreach (array_reverse($this->middlewares) as $middleware) {
$handler = function ($request) use ($middleware, $handler) {
return $middleware->handle($request, $handler);
};
}

// Ejecución del pipeline completo
return $handler($request);
}

10.2. Middlewares del Sistema

TransactionStartMiddleware

Inicia el registro de una transacción con datos de la solicitud:

namespace App\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;

use function App\Helpers\generateUUID;

class TransactionStartMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
$transaction = [
'method' => $request->getMethod(),
'url' => $request->getUri(),
'timestamp' => new \DateTime(),
'host' => $request->getHost(),
'origin' => $request->headers->get('origin'),
'referer' => $request->headers->get('referer'),
'user_agent' => $request->headers->get('user-agent'),
'ip' => $request->getClientIp(),
'ips' => $request->getClientIps(),
'nombre_usuario' => null,
'cod_usuario_ext' => null,
'num_usuario' => null,
'actions' => [],
'payload' => [
'body' => $request->request->all(),
'query' => $request->query->all(),
],
'request_id' => generateUUID()
];

$request->attributes->set('transaction', $transaction);
$request->attributes->set('actions', []);

return $next($request);
}
}

AuthMiddleware

Verifica la autenticación del usuario, mediante tokens JWT o sesiones:

// App/Middlewares/AuthMiddleware.php (resumen)
class AuthMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
$authorizationHeader = $request->headers->get('Authorization');
$traitRefresh = $request->headers->get('TraitRefresh');

if ($authorizationHeader || $traitRefresh) {
return $this->apiAuth($request, $next);
} else {
return $this->innerAuth($request, $next);
}
}

private function apiAuth(Request $request, callable $next)
{
$authorizationHeader = $request->headers->get('Authorization');
$token = trim(str_replace('Bearer', '', $authorizationHeader));
$decode = decodeToken($token);
$sessionId = $decode["data"]["session_id"];

// Verificaciones y establecimiento de sesión...

return $next($request);
}

// Otros métodos...
}

RateLimitMiddleware

Limita la cantidad de solicitudes por IP en un intervalo de tiempo:

// App/Middlewares/RateLimitMiddleware.php (extracto)
class RateLimitMiddleware implements Middleware
{
protected $pdo;
protected $limit = 100; // Predeterminado
protected $duration = 60; // Predeterminado (segundos)

public function handle(Request $request, callable $next)
{
$this->connectDatabase();
$clientIp = $request->getClientIp();
$currentTime = time();

// Verificar límites para la IP
$stmt = $this->pdo->prepare("
SELECT
qupapiratlim_request_count,
UNIX_TIMESTAMP(CONCAT(qupapiratlim_last_request_date, ' ', qupapiratlim_last_request_time)) AS last_request_unix
FROM qupapiratlim
WHERE qupapiratlim_ip_address = ?
");
$stmt->execute([$clientIp]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);

if ($row) {
if ($currentTime - $row['last_request_unix'] < $this->duration) {
if ($row['qupapiratlim_request_count'] >= $this->limit) {
throw new Exception('Too Many Requests', 429);
}
// Incrementar contador
// ...
} else {
// Resetear contador
// ...
}
} else {
// Crear nuevo registro
// ...
}

return $next($request);
}

// Otros métodos...
}

10.3. Implementación de Middlewares Personalizados

Para crear un middleware personalizado:

  1. Crear una clase que implemente la interfaz Middleware
  2. Implementar el método handle(Request $request, callable $next)
  3. Registrar el middleware en el kernel o en rutas específicas

Ejemplo de middleware personalizado:

namespace App\Middlewares\Custom;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;

class CustomHeaderMiddleware implements Middleware
{
protected $headerName;
protected $headerValue;

public function __construct($headerName, $headerValue)
{
$this->headerName = $headerName;
$this->headerValue = $headerValue;
}

public function handle(Request $request, callable $next)
{
// Procesamiento antes de la solicitud
$request->headers->set($this->headerName, $this->headerValue);

// Llama al siguiente middleware y obtiene la respuesta
$response = $next($request);

// Procesamiento después de la respuesta
$response->headers->set($this->headerName, $this->headerValue);

return $response;
}
}

10.4. Procesamiento de Transacciones

El sistema incluye middlewares específicos para el seguimiento de transacciones:

// App/Middlewares/TransactionEndMiddleware.php (resumen)
class TransactionEndMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
// Obtener respuesta del siguiente middleware
$response = $next($request);

// Obtener datos de la respuesta
$str = $response->getContent();
$res = json_decode($str, true);

// Completar información de la transacción
$transaction = $request->attributes->get('transaction');
$params = $request->attributes->get('params');
$transaction['response'] = $res;
$transaction['payload']['params'] = $params;
$transaction['payload']['content'] = $request->getContent();
$transaction['actions'] = QuApp::getActions();

$transaction['nombre_usuario'] = $request->attributes->get("nombre_usuario");
$transaction['cod_usuario_ext'] = $request->attributes->get("cod_usuario_ext");
$transaction['num_usuario'] = $request->attributes->get("num_usuario");

$request->attributes->set('transaction', $transaction);

return $response;
}
}

Dependencias

  • App\Core\RequestFactory: Proporciona acceso a los datos de la solicitud HTTP
  • App\Core\QuKernel: Registra y gestiona la cadena de middlewares
  • App\Core\Router\Route: Para middlewares asociados a rutas específicas
  • App\Helpers: Funciones auxiliares para procesamiento de datos

Consideraciones Técnicas

Orden de Ejecución

El orden de registro de middlewares es crucial, ya que determina su secuencia de ejecución. Los middlewares se ejecutan en el orden inverso al que se registran, siguiendo un patrón de "muñeca rusa" (nested dolls).

Interceptación de Excepciones

Los middlewares pueden interceptar excepciones lanzadas por middlewares posteriores o por el controlador, permitiendo implementar manejo centralizado de errores.

Rendimiento

Cada middleware adicional introduce una pequeña sobrecarga. Es recomendable limitar el número de middlewares globales y utilizar middlewares específicos por ruta cuando sea posible.

Modificación del Estado

Los middlewares tienen acceso completo a la solicitud y pueden modificarla. Esto debe hacerse con cuidado para evitar comportamientos inesperados.

Decisiones de Diseño

  1. Interfaz Única: Se eligió una interfaz simple con un solo método handle() para mantener la flexibilidad y simplicidad.

  2. Patrón Chain of Responsibility: Permite que cada middleware decida si procesar la solicitud y cómo hacerlo, o pasarla al siguiente en la cadena.

  3. Middlewares por Ruta: Permite aplicar middlewares específicos solo a las rutas que los necesitan, mejorando el rendimiento general.

  4. Captura del Estado de Transacción: Los middlewares TransactionStart/End permiten registrar el estado completo de cada solicitud para auditoría y depuración.