Cod în jurul fiecărui handler
Middleware este cod care rulează în jurul handlerelor tale: înainte ca cererea să ajungă la ele, după ce răspunsul pleacă de la ele sau în locul lor. Jurnalizare, autentificare, limitarea ritmului cererilor, compresie — toate sunt middleware.
Aceeași idee circulă sub trei nume: framework-urile web spun middleware, gRPC spune interceptor, unele ecosisteme spun filtru. Un concept, trei vocabulare.
În PHP, un middleware este o funcție care primește un handler — orice callable — și returnează un callable împachetat: exact șablonul Decorator din grupul despre șabloane de proiectare, iar o stivă de astfel de funcții se comportă ca un Chain of Responsibility.
Un middleware de jurnalizare
withLog împachetează orice handler: scrie în jurnal cererea la intrare, lasă handlerul să lucreze și scrie din nou la ieșire. Handlerul însuși rămâne complet neștiutor.
Serverul rulează ca un proces separat, așa că jurnalul lui nu poate ajunge singur în terminalul demonstrației — middleware-ul adaugă liniile într-un fișier de jurnal mic, iar demonstrația afișează liniile proaspete după fiecare cerere.
Împachetează o dată, jurnalizează tot
Handlerul împachetat servește două căi diferite și ambele ajung în jurnal — jurnalizarea este scrisă o singură dată, în împachetare, nu în fiecare handler.
Ieșire: script PHP
<?php
// Middleware: primește un handler, returnează un handler împachetat.
$router = <<<'PHP'
<?php
function withLog(callable $next): callable {
return function () use ($next) {
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
serverLog("-> {$_SERVER['REQUEST_METHOD']} $path");
$next();
serverLog("<- gata");
};
}
function serverLog(string $line): void {
file_put_contents(sys_get_temp_dir() . "/pc-logging-middleware.log", $line . "\n", FILE_APPEND);
}
$routes = [
"/users" => function () { echo "Ana, Ben\n"; },
"/notes" => function () { echo "shopping, ideas\n"; },
];
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
$handler = withLog($routes[$path] ?? function () { http_response_code(404); });
$handler();
PHP;
$logFile = sys_get_temp_dir() . "/pc-logging-middleware.log";
file_put_contents($logFile, "");
$routerFile = sys_get_temp_dir() . "/pc-logging-middleware.php";
file_put_contents($routerFile, $router);
// Un port liber, apoi serverul web încorporat al PHP ca proces-copil.
$probe = stream_socket_server("tcp://127.0.0.1:0");
$port = (int) explode(":", stream_socket_get_name($probe, false))[1];
fclose($probe);
$server = proc_open(["php", "-S", "127.0.0.1:$port", $routerFile],
[1 => ["file", "/dev/null", "w"], 2 => ["file", "/dev/null", "w"]], $pipes);
do {
usleep(20000);
$up = @fsockopen("127.0.0.1", $port);
} while ($up === false);
fclose($up);
$base = "http://127.0.0.1:$port";
$context = stream_context_create(["http" => ["ignore_errors" => true]]);
foreach (["/users", "/notes"] as $path) {
$body = file_get_contents("$base$path", false, $context);
echo file_get_contents($logFile); // ce a scris în jurnal middleware-ul
file_put_contents($logFile, "");
echo "corp: " . trim($body) . "\n";
}
proc_terminate($server);
proc_close($server);
unlink($routerFile);
unlink($logFile);
Middleware care oprește cererea
Un middleware poate și să refuze: o verificare de autentificare care nu găsește o cheie valabilă răspunde 401 Unauthorized și nu apelează niciodată handlerul următor.
Înlănțuit ca withLog(withAuth($handler)), cererea trece prin straturi în ordine — iar răspunsul iese la suprafață înapoi prin ele.
Un strat de autentificare în lanț
Cererea fără cheie este oprită de stratul de autentificare cu 401 — handlerul nu rulează niciodată. Cu cheia, cererea trece prin tot lanțul și ajunge la date.
Ieșire: script PHP
<?php
// withAuth poate opri cererea înainte ca handlerul să ruleze vreodată.
$router = <<<'PHP'
<?php
function withLog(callable $next): callable {
return function () use ($next) {
$path = parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH);
serverLog("-> {$_SERVER['REQUEST_METHOD']} $path");
$next();
};
}
// Middleware de autentificare: fără cheie valabilă, fără handler următor.
function withAuth(callable $next): callable {
return function () use ($next) {
if (($_SERVER["HTTP_X_API_KEY"] ?? "") !== "secret123") {
http_response_code(401);
echo "cheie API lipsă sau greșită\n";
return;
}
$next();
};
}
function serverLog(string $line): void {
file_put_contents(sys_get_temp_dir() . "/pc-auth-middleware.log", $line . "\n", FILE_APPEND);
}
$handler = withLog(withAuth(function () { echo "raportul secret\n"; }));
$handler();
PHP;
function httpRequest(string $url, string $method = "GET", array $headers = [], ?string $body = null): array {
$options = ["method" => $method, "ignore_errors" => true];
if ($headers !== []) {
$options["header"] = implode("\r\n", $headers);
}
if ($body !== null) {
$options["content"] = $body;
}
$responseBody = file_get_contents($url, false, stream_context_create(["http" => $options]));
return [(int) explode(" ", $http_response_header[0])[1], $responseBody, $http_response_header];
}
$logFile = sys_get_temp_dir() . "/pc-auth-middleware.log";
file_put_contents($logFile, "");
$routerFile = sys_get_temp_dir() . "/pc-auth-middleware.php";
file_put_contents($routerFile, $router);
// Un port liber, apoi serverul web încorporat al PHP ca proces-copil.
$probe = stream_socket_server("tcp://127.0.0.1:0");
$port = (int) explode(":", stream_socket_get_name($probe, false))[1];
fclose($probe);
$server = proc_open(["php", "-S", "127.0.0.1:$port", $routerFile],
[1 => ["file", "/dev/null", "w"], 2 => ["file", "/dev/null", "w"]], $pipes);
do {
usleep(20000);
$up = @fsockopen("127.0.0.1", $port);
} while ($up === false);
fclose($up);
$base = "http://127.0.0.1:$port";
foreach ([[], ["X-API-Key: secret123"]] as $requestHeaders) {
[$status, $body] = httpRequest("$base/report", "GET", $requestHeaders);
echo file_get_contents($logFile); // ce a scris în jurnal middleware-ul
file_put_contents($logFile, "");
echo " status: $status - " . trim($body) . "\n";
}
proc_terminate($server);
proc_close($server);
unlink($routerFile);
unlink($logFile);