[FIX] simulate: proc_open streaming SSE (pattern lg231) + NIS2_SSE flag
- public/simulate-nis2.php: riscritta con proc_open come lg231 test-runner. Lancia simulate-nis2.php come subprocess CLI con NIS2_SSE=1, streama ogni riga SSE al browser immediatamente senza buffering Apache/FPM. Stderr del subprocess → eventi SSE 'error' visibili nel terminale. - simulate-nis2.php: aggiunto supporto NIS2_SSE=1 (env var). Quando NIS2_SSE=1, IS_CLI=false → output SSE anche da sottoprocesso. API_BASE usa sempre server prod in modalità subprocess. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0a3f2d15e2
commit
49c62ab811
@ -1,27 +1,102 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* NIS2 Agile — Wrapper pubblico per simulate-nis2.php
|
* NIS2 Agile — Wrapper SSE per simulate-nis2.php (pattern proc_open, lg231-inspired)
|
||||||
*
|
*
|
||||||
* DocumentRoot è public/ → il file principale è fuori dalla web root.
|
* DocumentRoot è public/ → il file principale è fuori dalla web root.
|
||||||
* Questo wrapper converte il query param ?sim= nell'env var NIS2_SIM
|
* Usa proc_open per eseguire il simulatore come sottoprocesso CLI con NIS2_SSE=1,
|
||||||
* e include il simulatore reale dalla root del progetto.
|
* streamando ogni riga SSE al browser man mano che viene prodotta (no buffering).
|
||||||
|
*
|
||||||
|
* Vantaggi rispetto a require diretto:
|
||||||
|
* - PHP-FPM gestisce solo il proxy SSE (leggero, non può fare timeout del simulatore)
|
||||||
|
* - Output reale in tempo reale (nessun buffering Apache/FPM)
|
||||||
|
* - Errori del simulatore visibili come eventi SSE
|
||||||
*
|
*
|
||||||
* URL: https://nis2.agile.software/simulate-nis2.php?sim=all
|
* URL: https://nis2.agile.software/simulate-nis2.php?sim=all
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// La simulazione completa richiede 8-12 minuti: rimuovi il limite di esecuzione PHP
|
|
||||||
set_time_limit(0);
|
set_time_limit(0);
|
||||||
ignore_user_abort(true);
|
ignore_user_abort(true);
|
||||||
ini_set('memory_limit', '256M');
|
ini_set('memory_limit', '64M');
|
||||||
|
|
||||||
// Mappa ?sim=sim06 → NIS2_SIM=SIM06 (unico filtro attivo nel simulatore)
|
// ── SSE headers ──────────────────────────────────────────────────────────────
|
||||||
|
header('Content-Type: text/event-stream');
|
||||||
|
header('Cache-Control: no-cache');
|
||||||
|
header('X-Accel-Buffering: no');
|
||||||
|
header('Connection: keep-alive');
|
||||||
|
ob_implicit_flush(true);
|
||||||
|
while (ob_get_level()) ob_end_flush();
|
||||||
|
|
||||||
|
// ── Parametri ────────────────────────────────────────────────────────────────
|
||||||
$simParam = strtolower(trim($_GET['sim'] ?? 'all'));
|
$simParam = strtolower(trim($_GET['sim'] ?? 'all'));
|
||||||
if ($simParam === 'sim06') {
|
$scriptPath = realpath(__DIR__ . '/../simulate-nis2.php');
|
||||||
putenv('NIS2_SIM=SIM06');
|
|
||||||
}
|
|
||||||
// sim01-sim05 e 'all': SIM_FILTER rimane null → esegue tutti gli scenari
|
|
||||||
|
|
||||||
// Includi il simulatore dalla root del progetto.
|
if (!$scriptPath || !is_file($scriptPath)) {
|
||||||
// __DIR__ nel file incluso punta alla directory di quel file (/var/www/nis2-agile/)
|
echo 'data: ' . json_encode(['t' => 'error', 'm' => 'simulate-nis2.php non trovato']) . "\n\n";
|
||||||
// quindi readEnvValue() trova correttamente .env nella root.
|
echo 'data: ' . json_encode(['t' => 'done', 'stats' => ['pass' => 0, 'fail' => 1, 'skip' => 0, 'warn' => 0]]) . "\n\n";
|
||||||
require __DIR__ . '/../simulate-nis2.php';
|
flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Ambiente subprocess ───────────────────────────────────────────────────────
|
||||||
|
// NIS2_SSE=1 → il simulatore usa output SSE anche quando php_sapi_name()='cli'
|
||||||
|
// NIS2_SIM=SIM06 → esegue solo lo scenario B2B (opzionale)
|
||||||
|
$env = [];
|
||||||
|
foreach ($_ENV ?: [] as $k => $v) {
|
||||||
|
if (is_string($v)) $env[$k] = $v;
|
||||||
|
}
|
||||||
|
$env['NIS2_SSE'] = '1';
|
||||||
|
if ($simParam === 'sim06') {
|
||||||
|
$env['NIS2_SIM'] = 'SIM06';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Avvia subprocess (pattern lg231 runCommand) ───────────────────────────────
|
||||||
|
$descriptors = [0 => ['pipe', 'r'], 1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
|
||||||
|
$cwd = dirname($scriptPath);
|
||||||
|
$cmd = PHP_BINARY . ' ' . escapeshellarg($scriptPath);
|
||||||
|
$proc = proc_open($cmd, $descriptors, $pipes, $cwd, $env);
|
||||||
|
|
||||||
|
if (!is_resource($proc)) {
|
||||||
|
echo 'data: ' . json_encode(['t' => 'error', 'm' => 'Impossibile avviare il simulatore']) . "\n\n";
|
||||||
|
echo 'data: ' . json_encode(['t' => 'done', 'stats' => ['pass' => 0, 'fail' => 1, 'skip' => 0, 'warn' => 0]]) . "\n\n";
|
||||||
|
flush();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($pipes[0]);
|
||||||
|
stream_set_blocking($pipes[1], false);
|
||||||
|
stream_set_blocking($pipes[2], false);
|
||||||
|
|
||||||
|
// ── Streaming riga per riga ──────────────────────────────────────────────────
|
||||||
|
// Il simulatore emette direttamente eventi SSE (data: {...}\n\n).
|
||||||
|
// Il wrapper li passa al browser immediatamente senza buffering.
|
||||||
|
while (true) {
|
||||||
|
$chunk = fread($pipes[1], 8192);
|
||||||
|
if ($chunk !== false && $chunk !== '') {
|
||||||
|
echo $chunk;
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stderr → errori PHP fatali visibili nel terminale SSE
|
||||||
|
$err = fread($pipes[2], 1024);
|
||||||
|
if ($err !== false && $err !== '') {
|
||||||
|
foreach (explode("\n", trim($err)) as $errLine) {
|
||||||
|
if (trim($errLine) !== '') {
|
||||||
|
echo 'data: ' . json_encode(['t' => 'error', 'm' => '[stderr] ' . trim($errLine)]) . "\n\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
$status = proc_get_status($proc);
|
||||||
|
if (!$status['running']) {
|
||||||
|
// Leggi eventuale output residuo dopo la fine del processo
|
||||||
|
$tail = stream_get_contents($pipes[1]);
|
||||||
|
if ($tail) { echo $tail; flush(); }
|
||||||
|
fclose($pipes[1]);
|
||||||
|
fclose($pipes[2]);
|
||||||
|
proc_close($proc);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
usleep(50000); // 50ms — bilanciamento latenza/CPU (come lg231)
|
||||||
|
}
|
||||||
|
|||||||
@ -32,15 +32,18 @@ declare(strict_types=1);
|
|||||||
define('SIM_VERSION', '1.0.0');
|
define('SIM_VERSION', '1.0.0');
|
||||||
define('DEMO_EMAIL', getenv('NIS2_DEMO_EMAIL') ?: 'demo@nis2agile.it');
|
define('DEMO_EMAIL', getenv('NIS2_DEMO_EMAIL') ?: 'demo@nis2agile.it');
|
||||||
define('DEMO_PWD', 'NIS2Demo2026!');
|
define('DEMO_PWD', 'NIS2Demo2026!');
|
||||||
define('IS_CLI', php_sapi_name() === 'cli');
|
|
||||||
|
// NIS2_SSE=1 → forza output SSE anche da CLI (usato dal wrapper public/ con proc_open)
|
||||||
|
$_sseMode = (bool)getenv('NIS2_SSE');
|
||||||
|
define('IS_CLI', php_sapi_name() === 'cli' && !$_sseMode);
|
||||||
define('IS_WEB', !IS_CLI);
|
define('IS_WEB', !IS_CLI);
|
||||||
|
|
||||||
// URL base API — rileva automaticamente ambiente
|
// URL base API — rileva automaticamente ambiente
|
||||||
if (IS_CLI) {
|
if (IS_CLI || $_sseMode) {
|
||||||
// Sul server Hetzner: Apache serve /var/www/nis2-agile su https://nis2.agile.software/
|
// CLI diretto o subprocess: usa server prod (o override da env)
|
||||||
define('API_BASE', getenv('NIS2_API_BASE') ?: 'https://nis2.agile.software/api');
|
define('API_BASE', getenv('NIS2_API_BASE') ?: 'https://nis2.agile.software/api');
|
||||||
} else {
|
} else {
|
||||||
// In web: usa URL relativo allo stesso host
|
// Web request diretto: ricostruisce URL dal $_SERVER
|
||||||
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
$proto = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||||
define('API_BASE', "{$proto}://{$host}/api");
|
define('API_BASE', "{$proto}://{$host}/api");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user